//////////////////////////////////////////////////////////////////
// Compose
//
// C program shell for rapid application development
// The Flying Pig!
// Started 17/7/2003
//////////////////////////////////////////////////////////////////

//////////////////////////////////////////////////////////////////
// Includes

#include "oslib/osmodule.h"
#include "oslib/messagetrans.h"
#include "oslib/wimp.h"
#include "oslib/dragasprite.h"
#include "oslib/macros.h"
#include "oslib/osfile.h"
#include "oslib/colourtrans.h"
#include "oslib/draw.h"

#include "flexlib/flex.h"

#include "Compose.h"

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <ctype.h>
#include <stddef.h>
#include <math.h>

#if defined _DEBUG
#include <kernel.h>
#endif

//////////////////////////////////////////////////////////////////
// Defines

#define WORDALIGN(b) (((b) + 3) & ~3)
#define RANGE(var,min,max)   if ((var) < (min)) var = min; \
                             if ((var) > (max)) var = max;

#define MENU_XPOS_OFFSET     (-64)
/* (96 + 44 * <menu items> + 24 * <menu separators>) */
#define ICONBAR_MENU_YPOS    (96 + 44 * 2 + 24 * 0)
#define RAMTRANSMIT_SIZE     (10 * 1024)
#define COMPONENT_MAX        (64)
#define LINK_MAX             (20)
#define LINKEND_WIDTH        (22)
#define LINKSTART_WIDTH      (22)
#define BEZIER_START_X       (320)
#define BEZIER_START_Y       (0)
#define BEZIER_END_X         (-320)
#define BEZIER_END_Y         (0)
#define BEZIER_SCALE         (BEZIER_START_X)
#define DRAG_ARROW_COLOUR    (os_COLOUR_WHITE ^ os_COLOUR_RED)
#define LINK_COLOUR          (os_COLOUR_BLUE)
#define LINK_LINEWIDTH       (8)
#define LINK_ARROWWIDTH      (3)
#define LINK_ARROWLENGTH     (4)
#define LINKEND_XCLICK       (-LINK_ARROWLENGTH * LINK_ARROWWIDTH * 2)
#define COMPONENT_DIR_MAX    (256)
#define LINK_BUFFER_SIZE     (1024*10)
#define TRANS_MAX            (10)
#define LINK_STUB_WIDTH      (20)
#define LINK_STUBARROWWIDTH  (1)
#define LINK_STUBARROWLENGTH (1)
#define LINK_STUBOUTXSUB     (-18)
#define LINK_STUBINXSUB      (-18)
#define LINK_STUBLINEWIDTH   (16)
#define LINKSTUB_COLOUR      (os_COLOUR_BLACK)
#define MAIN_PANE_HEIGHT     (40)
#define POLL_DELAY           (10)
#define INFOTEXT_LEN         (1024)
#define INVALID              (-1)
#define INFO_MAX             (64)
#define INFO_MEM_MAX         (3*1024)
#define LOAD_SHIFT_SHIFT     (40)
#define LOAD_SHIFT_MAX       (400)

//////////////////////////////////////////////////////////////////
// Structures

typedef struct _LinkOut
{
  int                         nComponentTo;
  int                         nLinkOut;
  int                         nLinkIn;
  char                        *pcData;
  int                         nDataSize;
  int                         nTransBuffer;
  int                         nTransRef;
  int                         nSearchLinkIn;
} LinkOut;


typedef struct _LinkIn
{
  int                         nComponentFrom;
  int                         nLinkOut;
  int                         nLinkIn;
  int                         nDataSent;
  int                         nTransRef;
  int                         nSearchLinkOut;
} LinkIn;

typedef struct _Component
{
  int                         nXPos;
  int                         nYPos;
  int                         nWidth;
  int                         nHeight;
  osspriteop_area             *pcSprites;
  osspriteop_trans_tab        *psTransTable;
  bool                        boHidden;
  int                         nMaxLinksOut;
  int                         nMaxLinksIn;
  LinkOut                     asLinksOut[LINK_MAX];
  int                         nLinksOut;
  LinkIn                      asLinksIn[LINK_MAX];
  int                         nLinksIn;
  char                        szDir[COMPONENT_DIR_MAX];
  wimp_t                      hTask;
  char                        *pcInfo;
  int                         anInfoOffset[INFO_MAX];
} Component;

typedef enum
{
  PANE_INVALID = -1,

  PANE_NONE,
  PANE_TOP,
  PANE_BASE,

  PANE_NUM
} PANE;

typedef enum
{
  STATUS_INVALID = -1,

  STATUS_GUESS,

  STATUS_NONE,
  STATUS_SOME,
  STATUS_READY,

  STATUS_NUM
} STATUS;

//////////////////////////////////////////////////////////////////
// Global variables

static char                   gszProgTitle[128];
static char                   gpcTemp[256];
static char                   *gpcMessages;
static wimp_w                 gwhMain;
static wimp_w                 gwhMaHe;
static wimp_w                 gwhSave;
static wimp_w                 gwhWarn;
static wimp_i                 gihIconBarIcon;
static int                    gnMenuWins = 0;
static wimp_w                 gawhMenuWinHandle[10];
static int                    gnMenuXpos = 0;
static int                    gnMenuYpos = 0;
static wimp_menu              *gpcMenuCurrent = NULL;
static wimp_menu              *gpcIconBarMenu;
static wimp_menu              *gpcWindowMenu;
static wimp_t                 gnTaskHandle;
static bool                   gboDrag = FALSE;
static char                   gacFontRef[255];
static int                    gnMessageMyRef = 0;
static SAVETYPE               geSaveType = SAVETYPE_INVALID;
static LOADTYPE               geLoadType = LOADTYPE_INVALID;
static int                    gnSaveFileType;
static char                   gszSaveString[255];
static char                   *gpcStructure;
static int                    gnStructureSize;
static MemFile                *gpsStructureMemFile;

static wimp_message_data_xfer gsMessageStore;
static wimp_t                 gthSender;
static char                   gpcRamTransBuffer[RAMTRANSMIT_SIZE];
static char                   *gpcLoadedFile = NULL;
static int                    gnLoadedFileSize;
static int                    gnMyRef;
static int                    gnRamTransRef;
static char *                 gpcRamTransPos;
static int                    gnRamTransLeft;
static Component              gasComponents[COMPONENT_MAX];
static int                    gnComponents;
static int                    gnComponentDrag;
static int                    gnDragLinkXStart;
static int                    gnDragLinkYStart;
static int                    gnDragLinkXEnd;
static int                    gnDragLinkYEnd;
static int                    gnComponentLinkTo;
static int                    gnLinkOut;

static int                    gnLinkHandle = -1;
static wimp_t                 ghLinkTask = NULL;

static int                    gnLoadTaskCount = -1;

static char                   gapcTransBuffer[TRANS_MAX][LINK_BUFFER_SIZE];
static bool                   gaboTransBufferUsed[TRANS_MAX];
static osspriteop_area        *gpcSprites;
static int                    ganComponentsLoaded;
static STATUS                 geStatus;
static bool                   gboInWindow;
static int                    gnInfoComponent;
static int                    gnInfoLinkIn;
static int                    gnInfoLinkOut;
static int                    gnLoadPosShift;
static char                   gszConfigFile[256];
static int                    gnConfigNum;

//////////////////////////////////////////////////////////////////
// Function prototypes

void RedrawWindow (wimp_block *pcBlock);
void RedrawMain (wimp_draw *psRedraw);
void ModeChange (void);
void GenerateFullTransTable (void);
void GenerateTransTable (osspriteop_trans_tab **ppsTransTable, osspriteop_area *pcSprites);
void ResetComponent (Component * psComponent);
void ReleaseComponent (Component * psComponent);
void MouseClickMain (int nXPos, int nYPos, wimp_mouse_state nButton);
void MoveComponent (int nComponent, int nXPos, int nYPos);
void DragLinkEndStart (int nComponent, int nXPos, int nYPos);
void DragLinkStartStart (int nComponent, int nXPos, int nYPos);
void DragLinkEndEnd (void);
void DragComponentStart (int nComponent);
void DragLinkUpdate (void);
void RedragLinkEndStart (int nComponent, int nLinkOut, int nXPos, int nYPos);
void NullPoll (wimp_block *pcBlock);
void PlotArrow (int nXStart, int nYStart, int nXEnd, int nYEnd);
void CreateFixedLink (int nComponentFrom, int nLinkFrom, int nComponentTo, int nLinkTo);
void ResetLinkOut (LinkOut * psLink);
void ResetLinkIn (LinkIn * psLink);
void ReleaseLinkOut (LinkOut * psLink);
void ReleaseLinkIn (LinkIn * psLink);
bool LoadComponent (char * szFileName, int nXPos, int nYPos);
void LoadAllComponents (void);
void RunAllComponents (void);
void QuitAllComponents (void);
void ConfigComponent (int nComponent);
void TaskInitialise (wimp_block *pcBlock);
void TaskCloseDown (wimp_block *pcBlock);
void ReceiveLinkForward (wimp_block *pcBlock);
int GetNextTransBuffer (void);
void PlotStubOut (int nXStart, int nYStart, int nXEnd, int nYEnd);
void PlotStubIn (int nXStart, int nYStart, int nXEnd, int nYEnd);
void OpenWindowInitPane (wimp_w whWindow, wimp_w whPane, PANE ePaneType);
void OpenWindowInitPaneCentreSize (wimp_w whWindow, wimp_w whPane, PANE ePaneType, int nWidth, int nHeight);
void OpenWindowInitPaneNew (wimp_w whWindow, wimp_w whPane, PANE ePaneType);
wimp_w LoadTemplateMaHe (char * szWindowTitle, osspriteop_area * pcSpriteArea);
void SetStatus (STATUS eStatus);
void RemoveLink (int nComponentFrom, int nComponentTo, int nLinkOut, int nLinkIn);
void RemoveComponent (int nComponent);
int MemMoveLinkOut (LinkOut * psLinkOutTo, LinkOut * psLinkOutFrom);
int MemMoveLinkIn (LinkIn * psLinkInTo, LinkIn * psLinkInFrom);
int MemMoveComponent (Component * psComponentTo, Component * psComponentFrom);
void ShowInfoComponent (int nComponent);
void ShowInfoLinkStart (int nComponent, int nLink);
void ShowInfoLinkEnd (int nComponent, int nLink);
void ShowInfoNone (void);
void ShowInfo (void);
MemFile * CreateStructureFile (void);
void LoadStructureFile (MemFile * psStructureFile);
void SendConfigLoad (int nComponent);


//////////////////////////////////////////////////////////////////
// Main application

// Respond to swi errors
inline void err (os_error * sError)
{
  if (sError)
  {
    ShowWarning (sError);
  }
}

// Main program
int main (int argc, char * * argv)
{
  wimp_block                  cBlock;
  wimp_version_no             nVersion;
  int                         nCount;
  wimp_event_no               nEvent;
  wimp_MESSAGE_LIST(19) sMessages = {{message_DATA_SAVE,
                                      message_DATA_SAVE_ACK,
                                      message_DATA_LOAD,
                                      message_DATA_OPEN,
                                      message_RAM_FETCH,
                                      message_RAM_TRANSMIT,
                                      message_MODE_CHANGE,
                                      message_HELP,
                                      message_URL_LAUNCH,
                                      message_TASK_INITIALISE,
                                      message_TASK_CLOSE_DOWN,
                                      message_LINK_CONTROL,
                                      message_LINK_OPEN,
                                      message_LINK_CLOSE,
                                      message_LINK_SEND,
                                      message_LINK_DATASAVE,
                                      message_LINK_RAMFETCH,
                                      message_LINK_RAMTRANSMIT,
                                      0u}};
  wimp_message_list *psUserMessages = (wimp_message_list*)&sMessages;
  int                         nComponent;
  wimp_poll_flags             uFlags;
  os_t                        nTime;
  MemFile                     *pcMemFile;

  // Load Messages file
  gpcMessages = osmodule_alloc (17 + sizeof(MESSAGES));
  strcpy(gpcMessages + 16, MESSAGES);

  messagetrans_open_file ((messagetrans_control_block*)gpcMessages,
    gpcMessages + 16, 0);

  // Initialise task
  gnTaskHandle = wimp_initialise (wimp_VERSION_RO3, Tag ("Tsk"),
    psUserMessages, & nVersion);

  // Initialise the flex memory
  strncpy (gszProgTitle, Tag("Tsk"), sizeof (gszProgTitle));
  flex_init (gszProgTitle, 0, 0);

  // Load sprites
  gpcSprites = LoadSprites (SPRITES);

  // Load templates
  for (nCount = 0; nCount < 256; nCount++)
  {
    gacFontRef[nCount] = 0;
  }
  wimp_open_template (TEMPLATES);
  gwhMain = LoadTemplate ("Main");
  gwhMaHe = LoadTemplateMaHe ("MainHead", gpcSprites);
  gwhSave = LoadTemplate ("Save");
  gwhWarn = LoadTemplate ("Warning");

  // Set up general variables
  gboInWindow = FALSE;
  gpcLoadedFile = NULL;
  gnLoadedFileSize = 0;
  gnComponents = 0;
  ganComponentsLoaded = 0;
  geStatus = STATUS_INVALID;
  ShowInfoNone ();
  gnLoadPosShift = 0;
  gszConfigFile[0] = 0;
  gnConfigNum = 0;

  for (nComponent = 0; nComponent < COMPONENT_MAX; nComponent++)
  {
    ResetComponent (& gasComponents[nComponent]);
  }
//  // Load a bogus component
//  for (gnComponents = 0; gnComponents < 7; gnComponents++)
//  {
//    gasComponents[gnComponents].nXPos = 300;
//    gasComponents[gnComponents].nYPos = -500;
//    gasComponents[gnComponents].nWidth = 126 * 2;
//    gasComponents[gnComponents].nHeight = 109 * 2;
//    gasComponents[gnComponents].pcSprites = LoadSprites
//      ("<Compose$Dir>.^.Components.!Test.Compose");
//    gasComponents[gnComponents].nMaxLinksIn = 3;
//    gasComponents[gnComponents].nMaxLinksOut = 5;
//  }
  gnLoadTaskCount = -1;
  for (nCount = 0; nCount < TRANS_MAX; nCount++)
  {
    gaboTransBufferUsed[nCount] = FALSE;
  }

  // Create menus
  gpcIconBarMenu = CreateMenu (Tag ("Menu1"));
  gpcWindowMenu = CreateMenu (Tag("Menu2"));

  SetSubMenu (gpcWindowMenu, 0, (wimp_menu*)gwhSave);

  // Close templates
  wimp_close_template ();

  // Set up the version info
  SetIconText (VERSION_STRING_LONG, gawhMenuWinHandle[0], 3);

  // Create icon
  gihIconBarIcon = CreateIconbarIcon (Tag ("Icn"), 76, 76);

  // Save structure memory
  gpcStructure = NULL;
  gnStructureSize = 0;
  gpsStructureMemFile = NULL;

  // Set up the status
  SetStatus (STATUS_GUESS);

  // Load a file of we ought to
  if (argc > 1)
  {
    pcMemFile = memopen (argv[1], "r");
    LoadStructureFile (pcMemFile);
    memclose (pcMemFile);
    SetIconText (argv[1], gwhSave, 2);
    OpenWindowInitPane (gwhMain, gwhMaHe, PANE_TOP);
    strncpy (gszConfigFile, argv[1], sizeof (gszConfigFile));
  }

  // Main polling loop
  //////////////////////////////////////////////////////////////////
  do
  {
    if ((geSaveType == SAVETYPE_LINKEND) || (gboInWindow))
    {
      uFlags = FLAGS_POLL;
    }
    else
    {
      uFlags = FLAGS_NOPOLL;
    }

    if (geSaveType == SAVETYPE_LINKEND)
    {
      nEvent = wimp_poll (uFlags, & cBlock, NULL);
    }
    else
    {
      nTime = os_read_monotonic_time () + POLL_DELAY;
      nEvent = wimp_poll_idle (uFlags, & cBlock, nTime, NULL);
    }

    switch (nEvent)
    {
      case  0: // Null
        NullPoll (& cBlock);
        break;
      case  1: // Redraw window
        RedrawWindow (& cBlock);
        break;
      case  2: // Open window
        OpenWindow (& cBlock);
        break;
      case  3: // Closewindow
        CloseWindow (& cBlock);
        break;
      case  4: // Pointer leaving window
        if (cBlock.leaving.w == gwhMain)
        {
          gboInWindow = FALSE;
          ShowInfoNone ();
        }
        break;
      case  5: // Pointer entering window
        if (cBlock.leaving.w == gwhMain)
        {
          gboInWindow = TRUE;
        }
        break;
      case  6:
        MouseClick (& cBlock);
        break;
      case  7: // User drag box
        UserDragBox (& cBlock);
        break;
      case  8: // Keys
        Keys (& cBlock);
        break;
      case  9:
        MenuSelect (& cBlock);
        break;
      case 10: // Scroll request
        break;
      case 11: // Lose caret
        break;
      case 12: // Gain caret
        break;
      case 17: // Receive message
      case 18:
        Receive (& cBlock);
        break;
      case 19: // UserMessage Acknowledge
        Acknowledged (& cBlock);
        break;
      default: // Default
        break;
    }
  } while TRUE;

  //////////////////////////////////////////////////////////////////

  return 0;
}

//////////////////////////////////////////////////////////////////
// Poll 16,17: Message received
void Receive (wimp_block *pcBlock)
{
  switch (pcBlock->message.action)
  {
    case message_QUIT:
      Quit ();
      break;
    case message_DATA_SAVE:
      DataSave (pcBlock);
      break;
    case message_DATA_SAVE_ACK:
      DataSaveAck (pcBlock);
      break;
    case message_DATA_LOAD:
      DataLoad (pcBlock);
      break;
    case message_DATA_OPEN:
      DataOpen (pcBlock);
      break;
    case message_RAM_FETCH:
      RamFetch (pcBlock);
      break;
    case message_RAM_TRANSMIT:
      RamTransmit (pcBlock);
      break;
    case message_MODE_CHANGE:
      ModeChange ();
    case message_HELP:
      Help (pcBlock);
      break;
    case message_TASK_INITIALISE:
      TaskInitialise (pcBlock);
      break;
    case message_TASK_CLOSE_DOWN:
      TaskCloseDown (pcBlock);
      break;

    case message_LINK_CONTROL:
      ReceiveLinkControl (pcBlock);
      break;
    case message_LINK_OPEN:
      ReceiveLinkForward (pcBlock);
      break;
    case message_LINK_CLOSE:
      ReceiveLinkForward (pcBlock);
      break;
    case message_LINK_SEND:
      ReceiveLinkForward (pcBlock);
      break;
    case message_LINK_DATASAVE:
      ReceiveLinkDataSave (pcBlock);
      break;
    case message_LINK_RAMFETCH:
      ReceiveLinkRamFetch (pcBlock);
      break;
    case message_LINK_RAMTRANSMIT:
      ReceiveLinkRamTransmit (pcBlock);
      break;
  }
}

//////////////////////////////////////////////////////////////////
// Poll 19: Message acknowledge received
void Acknowledged (wimp_block *pcBlock)
{
  switch (pcBlock->message.action)
  {
    case message_RAM_FETCH:
      RamFetchReturned (pcBlock);
      break;
    case message_URL_LAUNCH:
      OpenURLReturned (pcBlock);
      break;
  }
  gnMessageMyRef = 0;
}

// Quit task
void Quit (void)
{
  int                         nCount;
  int                         nRemove;

  // Free the fonts
  for (nCount = 0; nCount < 256; nCount++)
  {
    if (gacFontRef[nCount])
    {
      for (nRemove = 0; nRemove < gacFontRef[nCount]; nRemove++)
      {
        xfont_lose_font ((font_f)nCount);
      }
    }
  }

  // Close the program
  wimp_close_down (gnTaskHandle);
}

//////////////////////////////////////////////////////////////////
// Poll 6: Process mouse clicks
void MouseClick (wimp_block *pcBlock)
{
  int                         nXPos       = pcBlock->pointer.pos.x;
  int                         nYPos       = pcBlock->pointer.pos.y;
  wimp_mouse_state            nButton     = pcBlock->pointer.buttons;
  wimp_w                      whWindow    = pcBlock->pointer.w;
  wimp_i                      ihIcon      = pcBlock->pointer.i;
  char                        *szFileIcon;
  wimp_window_state           sState;
  int                         nWinXPos;
  int                         nWinYPos;

  sState.w = whWindow;
  xwimp_get_window_state (& sState);
  nWinXPos = nXPos - sState.visible.x0 + sState.xscroll;
  nWinYPos = nYPos - sState.visible.y1 + sState.yscroll;

  if (whWindow == wimp_ICON_BAR)
  {
    switch (nButton)
    {
      case wimp_CLICK_SELECT:
        OpenWindowInitPane (gwhMain, gwhMaHe, PANE_TOP);
        break;
      case wimp_CLICK_MENU:
        OpenMenu (gpcIconBarMenu, nXPos + MENU_XPOS_OFFSET,
          ICONBAR_MENU_YPOS);
        break;
    }
  }

  if (whWindow == gwhMain)
  {
    if (nButton == wimp_CLICK_MENU)
    {
      OpenMenu (gpcWindowMenu, nXPos - 64, nYPos);
    }
    else
    {
      switch (ihIcon)
      {
        case wimp_ICON_WINDOW:
          // Click in the main window area
          MouseClickMain (nWinXPos, nWinYPos, nButton);
          break;
        default: // Save drag
          if (nButton & (wimp_DRAG_SELECT | wimp_DRAG_ADJUST))
          {
            strncpy (gszSaveString, GetIconText (whWindow, ihIcon),
              sizeof(gszSaveString));
            DragBox (whWindow, ihIcon);
            geSaveType = SAVETYPE_ICONTEXT;
          }
          break;
      }
    }
  }

  if (whWindow == gwhMaHe)
  {
    if (nButton == wimp_CLICK_MENU)
    {
      OpenMenu (gpcWindowMenu, nXPos - 64, nYPos);
    }
    else
    {
      switch (ihIcon)
      {
        case (wimp_i)3: // Load
          LoadAllComponents ();
          break;
        case (wimp_i)4: // Run
          RunAllComponents ();
          break;
        case (wimp_i)5: // Quit
          QuitAllComponents ();
          break;
        default: // Save drag
          if (nButton & (wimp_DRAG_SELECT | wimp_DRAG_ADJUST))
          {
            strncpy (gszSaveString, GetIconText (whWindow, ihIcon),
              sizeof(gszSaveString));
            DragBox (whWindow, ihIcon);
            geSaveType = SAVETYPE_ICONTEXT;
          }
          break;
      }
    }
  }

  if (whWindow == gwhSave)
  {
    switch (ihIcon)
    {
      case (wimp_i)3: // Save drag
        if (nButton & (wimp_DRAG_SELECT | wimp_DRAG_ADJUST))
        {
          szFileIcon = GetIconText (gwhSave, 3);
          gnSaveFileType = COMPOSE_FILETYPE;
          DragSprite (whWindow, ihIcon, szFileIcon);
          geSaveType = SAVETYPE_FILE;
        }
        break;
      case (wimp_i)1: // Cancel
        CloseMenu ();
        break;
      case (wimp_i)0: // Save
        gnSaveFileType = COMPOSE_FILETYPE;
        SaveSave ();
        if (nButton != wimp_CLICK_ADJUST)
        {
          CloseMenu ();
        }
        break;
    }
  }

  if (whWindow == gwhWarn)
  {
    switch (ihIcon)
    {
      case (wimp_i)0: // OK
        CloseWindowHandle (gwhWarn);
        break;
    }
  }

  if (whWindow == gawhMenuWinHandle[0])
  {
    switch (ihIcon)
    {
      case (wimp_i)8: // Email
        OpenURL (Tag("Email"));
        if (nButton != wimp_CLICK_ADJUST)
        {
          CloseMenu ();
        }
        break;
      case (wimp_i)9: // Website
        OpenURL (Tag("Website"));
        if (nButton != wimp_CLICK_ADJUST)
        {
          CloseMenu ();
        }
        break;
    }
  }
}

//////////////////////////////////////////////////////////////////
// Poll 8: Act on user key presses
void Keys (wimp_block *pcBlock)
{
  wimp_w                      whWindow;
  wimp_i                      ihIcon;
  wimp_key_no                 nKey;

  whWindow = pcBlock->key.w;
  ihIcon = pcBlock->key.i;
  nKey = pcBlock->key.c;

  switch (nKey)
  {
    case wimp_KEY_RETURN:
      if (whWindow == gwhSave)
      {
        gnSaveFileType = COMPOSE_FILETYPE;
        SaveSave ();
        CloseMenu ();
      }
      if (whWindow == gwhWarn)
      {
        CloseWarning ();
      }
      break;
    case wimp_KEY_ESCAPE:
      if (whWindow == gwhWarn)
      {
        CloseWarning ();
      }
      break;
    default:
      xwimp_process_key (nKey);
      break;
  }
}

//////////////////////////////////////////////////////////////////
// Poll 9: Act on menu selections
void MenuSelect (wimp_block *pcBlock)
{
  wimp_pointer                sPointer;

  xwimp_get_pointer_info (& sPointer);

  if (gpcMenuCurrent == gpcIconBarMenu)
  {
    switch (pcBlock->selection.items[0])
    {
      case 1: // Quit
        Quit ();
        break;
      default:
        break;
    }
  }

  if (gpcMenuCurrent == gpcWindowMenu)
  {
    switch (pcBlock->selection.items[0])
    {
      case 1: // Load all components
        LoadAllComponents ();
        break;
      case 2: // Run all components
        RunAllComponents ();
        break;
      case 3: // Quit all components
        QuitAllComponents ();
        break;
      default:
        break;
    }
  }

  if (sPointer.buttons & wimp_CLICK_ADJUST)
  {
    OpenMenu (gpcMenuCurrent, gnMenuXpos, gnMenuYpos);
  }
}

//////////////////////////////////////////////////////////////////
// Poll 7: User has finished dragging an object
void UserDragBox (wimp_block *pcBlock)
{
  char                        *szFilename;
  wimp_w                      whWindow;
  wimp_pointer                sPointer;
  wimp_message                sMessage;
  wimp_auto_scroll_info       sScrollInfo;

  xdragasprite_stop ();
  gboDrag = FALSE;

  xwimp_get_pointer_info (& sPointer);

  whWindow = sPointer.w;

  if ((whWindow != gwhMain)
     && (whWindow != gwhSave)
     && (whWindow != gwhWarn))
  {
    switch (geSaveType)
    {
      case SAVETYPE_FILE:
        sMessage.data.data_xfer.w = whWindow;
        sMessage.data.data_xfer.i = sPointer.i;
        sMessage.data.data_xfer.pos = sPointer.pos;
        sMessage.data.data_xfer.est_size = gnStructureSize;
        sMessage.data.data_xfer.file_type = gnSaveFileType;

        szFilename = GetIconText (gwhSave, 2);
        while (strchr (szFilename, '.'))
        {
          szFilename = strchr(szFilename, '.') + 1;
        }

        strncpy (sMessage.data.data_xfer.file_name, szFilename, 211);
        sMessage.data.data_xfer.file_name[211] = 0;

        sMessage.size = WORDALIGN((44 + 1 + strlen (szFilename)));
        sMessage.your_ref = 0;
        sMessage.action = 1;

        xwimp_send_message_to_window (wimp_USER_MESSAGE, & sMessage,
                   whWindow, sPointer.i, NULL);
        break;
      case SAVETYPE_ICONTEXT:
        sMessage.data.data_xfer.w = whWindow;
        sMessage.data.data_xfer.i = sPointer.i;
        sMessage.data.data_xfer.pos = sPointer.pos;
        sMessage.data.data_xfer.est_size = strlen(gszSaveString) + 1;
        sMessage.data.data_xfer.file_type = 0xfff;

        strncpy (sMessage.data.data_xfer.file_name, "IconText", 12);
        sMessage.data.data_xfer.file_name[11] = 0;

        sMessage.size = WORDALIGN((44 + 12));
        sMessage.your_ref = 0;
        sMessage.action = 1;

        xwimp_send_message_to_window (wimp_USER_MESSAGE, & sMessage,
          whWindow, sPointer.i, NULL);
        break;
      case SAVETYPE_COMPONENT:
        sScrollInfo.w = gwhMain;
        sScrollInfo.pause_zone_sizes.x0 = 80;
        sScrollInfo.pause_zone_sizes.y0 = 80;
        sScrollInfo.pause_zone_sizes.x1 = 80;
        sScrollInfo.pause_zone_sizes.y1 = 80;
        sScrollInfo.pause_duration = 0;
        sScrollInfo.state_change = NULL;
        sScrollInfo.handle = NULL;
        xwimp_auto_scroll (0, & sScrollInfo, NULL);
        // Delete the component
        RemoveComponent (gnComponentDrag);
        geSaveType = SAVETYPE_INVALID;
        break;
      case SAVETYPE_LINKEND:
        DragLinkEndEnd ();
        geSaveType = SAVETYPE_INVALID;
        break;
      default:
        break;
    }
  }
  if (whWindow == gwhMain)
  {
    switch (geSaveType)
    {
      case SAVETYPE_COMPONENT:
        sScrollInfo.w = gwhMain;
        sScrollInfo.pause_zone_sizes.x0 = 80;
        sScrollInfo.pause_zone_sizes.y0 = 80;
        sScrollInfo.pause_zone_sizes.x1 = 80;
        sScrollInfo.pause_zone_sizes.y1 = 80;
        sScrollInfo.pause_duration = 0;
        sScrollInfo.state_change = NULL;
        sScrollInfo.handle = NULL;
        xwimp_auto_scroll (0, & sScrollInfo, NULL);
        gasComponents[gnComponentDrag].boHidden = FALSE;
        MoveComponent (gnComponentDrag, pcBlock->dragged.final.x0,
          pcBlock->dragged.final.y0);
        geSaveType = SAVETYPE_INVALID;
        break;
      case SAVETYPE_LINKEND:
        DragLinkEndEnd ();
        geSaveType = SAVETYPE_INVALID;
        break;
      default:
        break;
    }
  }
}

//////////////////////////////////////////////////////////////////
// Opens the pane for a window
void OpenWindowPane (PANE ePaneType, wimp_w whPane, wimp_open * psMainOpen, wimp_open * psPaneOpen, int nHeight)
{
  switch (ePaneType)
  {
    default:
    case PANE_TOP:
      *psPaneOpen = *psMainOpen;
      psPaneOpen->w = whPane;
      xwimp_open_window (psPaneOpen);
      psMainOpen->next = psPaneOpen->w;
      break;
    case PANE_BASE:
      *psPaneOpen = *psMainOpen;
      psPaneOpen->visible.y1 = psMainOpen->visible.y0 + nHeight;
      psPaneOpen->w = whPane;
      xwimp_open_window (psPaneOpen);
      psMainOpen->next = psPaneOpen->w;
      break;
  }
}

//////////////////////////////////////////////////////////////////
// Poll 2: Open window given window block
void OpenWindow (wimp_block *pcBlock)
{
  wimp_w                      whWindow;
  bool                        boWinFound;
  wimp_open                   sPaneOpen;
  PANE                        ePaneType;
  int                         nHeight = 0;

  whWindow = pcBlock->open.w;

  ePaneType = PANE_NONE;
  boWinFound = FALSE;
  if (whWindow == gwhMain)
  {
    ePaneType = PANE_TOP;
    boWinFound = TRUE;
    nHeight = MAIN_PANE_HEIGHT;
    OpenWindowPane (ePaneType, gwhMaHe, & pcBlock->open, & sPaneOpen,
      nHeight);
  }

  xwimp_open_window (& pcBlock->open);

  if (ePaneType != PANE_NONE)
  {
    switch (ePaneType)
    {
      case PANE_TOP:
        // Check to see whether the pane is in the correct position
        if ((sPaneOpen.visible.x0 != pcBlock->open.visible.x0)
          || (sPaneOpen.visible.y1 != pcBlock->open.visible.y1)
          || (sPaneOpen.visible.x1 != pcBlock->open.visible.x1))
        {
          sPaneOpen.visible = pcBlock->open.visible;
          xwimp_open_window (& sPaneOpen);
        }
        break;
      case PANE_BASE:
        // Check to see whether the pane is in the correct position
        if ((sPaneOpen.visible.x0 != pcBlock->open.visible.x0)
          || (sPaneOpen.visible.y0 != pcBlock->open.visible.y0)
          || (sPaneOpen.visible.x1 != pcBlock->open.visible.x1))
        {
          sPaneOpen.visible = pcBlock->open.visible;
          sPaneOpen.visible.y1 = pcBlock->open.visible.y0 + nHeight;
          xwimp_open_window (& sPaneOpen);
        }
        break;
      default:
        // Do nothing
        break;
    }
  }
}

//////////////////////////////////////////////////////////////////
// Open a window on screen that is currently closed
void OpenWindowInit(wimp_w whWindow)
{
  wimp_window_state           sState;

  sState.w = whWindow;
  xwimp_get_window_state (& sState);
  sState.next = wimp_TOP;
  xwimp_open_window ((wimp_open *)& sState);
}

//////////////////////////////////////////////////////////////////
// Open a window on screen that is currently closed with its pane
void OpenWindowInitPane (wimp_w whWindow, wimp_w whPane, PANE ePaneType)
{
  wimp_window_state           sState;
  wimp_window_state           sPane;

  sState.w = whWindow;
  xwimp_get_window_state (& sState);
  sState.next = wimp_TOP;
  xwimp_open_window ((wimp_open *)& sState);

  switch (ePaneType)
  {
    default:
    case PANE_TOP:
      sState.w = whPane;
      xwimp_open_window ((wimp_open *)& sState);
      break;
    case PANE_BASE:
      sPane.w = whPane;
      xwimp_get_window_state (& sPane);
      sState.w = whPane;
      sState.visible.y1
        = sState.visible.y0 + (sPane.visible.y1 - sPane.visible.y0);
      xwimp_open_window ((wimp_open *)& sState);
      break;
  }
}

//////////////////////////////////////////////////////////////////
// Open a window on screen that is currently closed with its pane
void OpenWindowInitPaneNew (wimp_w whWindow, wimp_w whPane, PANE ePaneType)
{
  wimp_window_state           sState;
  wimp_window_state           sPane;

  sState.w = whWindow;
  xwimp_get_window_state (& sState);
  sState.yscroll = 0;
  sState.next = wimp_TOP;
  xwimp_open_window ((wimp_open *)& sState);

  switch (ePaneType)
  {
    default:
    case PANE_TOP:
      sState.w = whPane;
      xwimp_open_window ((wimp_open *)& sState);
      break;
    case PANE_BASE:
      sPane.w = whPane;
      xwimp_get_window_state (& sPane);
      sState.w = whPane;
      sState.visible.y1
        = sState.visible.y0 + (sPane.visible.y1 - sPane.visible.y0);
      xwimp_open_window ((wimp_open *)& sState);
      break;
  }
}

//////////////////////////////////////////////////////////////////
// Open a window in the centre of the screen
void OpenWindowInitCentre(wimp_w whWindow)
{
  int                         nHeight;
  int                         nWidth;
  int                         nEigFactor;
  int                         nSize;
  int                         nPosition;
  wimp_window_state           sState;

  sState.w = whWindow;
  xwimp_get_window_state (& sState);
  sState.next = wimp_TOP;

  nWidth = sState.visible.x1 - sState.visible.x0;
  nHeight = sState.visible.y1 - sState.visible.y0;

  xos_read_mode_variable (os_CURRENT_MODE, os_MODEVAR_XEIG_FACTOR,
    & nEigFactor, NULL);
  xos_read_mode_variable (os_CURRENT_MODE, os_MODEVAR_XWIND_LIMIT,
    & nSize, NULL);
  nPosition = ((nSize << nEigFactor) - nWidth) / 2;
  sState.visible.x0 = nPosition;
  sState.visible.x1 = nPosition + nWidth;

  xos_read_mode_variable (os_CURRENT_MODE, os_MODEVAR_YEIG_FACTOR,
    & nEigFactor, NULL);
  xos_read_mode_variable (os_CURRENT_MODE, os_MODEVAR_YWIND_LIMIT,
    & nSize, NULL);
  nPosition = ((nSize << nEigFactor) - nHeight) / 2;
  sState.visible.y0 = nPosition;
  sState.visible.y1 = nPosition + nHeight;

  xwimp_open_window ((wimp_open *)& sState);
}

//////////////////////////////////////////////////////////////////
// Open a window in the centre of the screen with a pane
void OpenWindowInitPaneCentreSize (wimp_w whWindow, wimp_w whPane, PANE ePaneType, int nWidth, int nHeight)
{
//  int                         nHeight;
//  int                         nWidth;
  int                         nEigFactor;
  int                         nSize;
  int                         nPosition;
  wimp_window_state           sState;
  wimp_window_state           sPane;

  sState.w = whWindow;
  xwimp_get_window_state (& sState);
  sState.next = wimp_TOP;
  sState.xscroll = 0;
  sState.yscroll = 0;

//  nWidth = sState.visible.x1 - sState.visible.x0;
//  nHeight = sState.visible.y1 - sState.visible.y0;

  xos_read_mode_variable (os_CURRENT_MODE, os_MODEVAR_XEIG_FACTOR,
    & nEigFactor, NULL);
  xos_read_mode_variable (os_CURRENT_MODE, os_MODEVAR_XWIND_LIMIT,
    & nSize, NULL);
  nPosition = ((nSize << nEigFactor) - nWidth) / 2;
  sState.visible.x0 = nPosition;
  sState.visible.x1 = nPosition + nWidth;

  xos_read_mode_variable (os_CURRENT_MODE, os_MODEVAR_YEIG_FACTOR,
    & nEigFactor, NULL);
  xos_read_mode_variable (os_CURRENT_MODE, os_MODEVAR_YWIND_LIMIT,
    & nSize, NULL);
  nPosition = ((nSize << nEigFactor) - nHeight) / 2;
  sState.visible.y0 = nPosition;
  sState.visible.y1 = nPosition + nHeight;

  xwimp_open_window ((wimp_open *)& sState);

  switch (ePaneType)
  {
    default:
    case PANE_TOP:
      sState.w = whPane;
      xwimp_open_window ((wimp_open *)& sState);
      break;
    case PANE_BASE:
      sPane.w = whPane;
      xwimp_get_window_state (& sPane);
      sState.w = whPane;
      sState.visible.y1
        = sState.visible.y0 + (sPane.visible.y1 - sPane.visible.y0);
      xwimp_open_window ((wimp_open *)& sState);
      break;
  }
}

//////////////////////////////////////////////////////////////////
// Open a given menu at the given position on screen
void OpenMenu (wimp_menu * pcMenu, int nXPos, int nYPos)
{
  xwimp_create_menu (pcMenu, nXPos, nYPos);

  gnMenuXpos = nXPos;
  gnMenuYpos = nYPos;
  gpcMenuCurrent = pcMenu;
}

//////////////////////////////////////////////////////////////////
// Poll 3: Close a window
void CloseWindow (wimp_block *pcBlock)
{
  wimp_w                      whWindow;

  whWindow = pcBlock->open.w;

  wimp_close_window (pcBlock->close.w);

  if (whWindow == gwhMain)
  {
    err (xwimp_close_window (whWindow));
    err (xwimp_close_window (gwhMaHe));
  }
}

//////////////////////////////////////////////////////////////////
// Close window given handle
void CloseWindowHandle (wimp_w whWindow)
{
  wimp_close_window (whWindow);
}

//////////////////////////////////////////////////////////////////
// Close the currently open menu
void CloseMenu (void)
{
  xwimp_create_menu ((wimp_menu*)-1, 0, 0);

  gnMenuXpos = 0;
  gnMenuYpos = 0;
  gpcMenuCurrent = NULL;
}

//////////////////////////////////////////////////////////////////
// Create an iconbar icon
wimp_i CreateIconbarIcon (char * szSprite, int nWidth, int nHeight)
{
  wimp_i                      ihIcon;
  wimp_icon_create            sIconBlock;

  sIconBlock.w = wimp_ICON_BAR_RIGHT;
  sIconBlock.icon.extent.x0 = 0;
  sIconBlock.icon.extent.y0 = 0;
  sIconBlock.icon.extent.x1 = nWidth;
  sIconBlock.icon.extent.y1 = nHeight;
  sIconBlock.icon.flags = wimp_ICON_SPRITE |
                   (wimp_BUTTON_CLICK << wimp_ICON_BUTTON_TYPE_SHIFT);
  strncpy(sIconBlock.icon.data.sprite, szSprite, 12);

  ihIcon = wimp_create_icon (& sIconBlock);

  return ihIcon;
}

//////////////////////////////////////////////////////////////////
// Create window from Templates file
wimp_w LoadTemplate (char * szWindowTitle)
{
  wimp_window                 *pcWindow;
  char *                      pcIndirected;
  int                         nWindowSize;
  int                         nIndirectedSize;
  char                        szTitle[12];
  wimp_w                      whWindow;

  wimp_load_template (0, NULL, NULL, NULL, szWindowTitle, 0,
    & nWindowSize, & nIndirectedSize);

  pcWindow = (wimp_window*)malloc (nWindowSize);
  pcIndirected = malloc (nIndirectedSize);
  strncpy (szTitle, szWindowTitle, 12);

  wimp_load_template (pcWindow, pcIndirected, pcIndirected + nIndirectedSize,
    gacFontRef, szWindowTitle, 0, NULL, NULL);

  whWindow = wimp_create_window (pcWindow);

  free((void *)pcWindow);

  return whWindow;
}

//////////////////////////////////////////////////////////////////
// Create window from Templates file with sprites
wimp_w LoadTemplateSprites (char * szWindowTitle, osspriteop_area * pcSpriteArea)
{
  wimp_window                 *pcWindow;
  char *                      pcIndirected;
  int                         nWindowSize;
  int                         nIndirectedSize;
  char                        szTitle[12];
  wimp_w                      whWindow;

  wimp_load_template (0, NULL, NULL, NULL, szWindowTitle, 0,
    & nWindowSize, & nIndirectedSize);

  pcWindow = (wimp_window*)malloc (nWindowSize);
  pcIndirected = malloc (nIndirectedSize);
  strncpy (szTitle, szWindowTitle, 12);

  wimp_load_template (pcWindow, pcIndirected, pcIndirected + nIndirectedSize,
    gacFontRef, szWindowTitle, 0, NULL, NULL);

  if (pcSpriteArea)
  {
    pcWindow->sprite_area = pcSpriteArea;
  }

  whWindow = wimp_create_window (pcWindow);

  free((void *)pcWindow);

  return whWindow;
}

//////////////////////////////////////////////////////////////////
// Create Main Head window from Templates file with sprites
wimp_w LoadTemplateMaHe (char * szWindowTitle, osspriteop_area * pcSpriteArea)
{
  wimp_window                 *pcWindow;
  char *                      pcIndirected;
  int                         nWindowSize;
  int                         nIndirectedSize;
  char                        szTitle[12];
  wimp_w                      whWindow;

  wimp_load_template (0, NULL, NULL, NULL, szWindowTitle, 0,
    & nWindowSize, & nIndirectedSize);

  pcWindow = (wimp_window*)malloc (nWindowSize);
  pcIndirected = malloc (nIndirectedSize);
  strncpy (szTitle, szWindowTitle, 12);

  wimp_load_template (pcWindow, pcIndirected, pcIndirected + nIndirectedSize,
    gacFontRef, szWindowTitle, 0, NULL, NULL);

  if (pcSpriteArea)
  {
    pcWindow->sprite_area = pcSpriteArea;
  }

  pcWindow->icons[0].data.indirected_sprite.area = pcSpriteArea;
  pcWindow->icons[1].data.indirected_sprite.area = pcSpriteArea;
  pcWindow->icons[2].data.indirected_sprite.area = pcSpriteArea;

  whWindow = wimp_create_window (pcWindow);

  free((void *)pcWindow);

  return whWindow;
}

//////////////////////////////////////////////////////////////////
// Create a menu from a text string - usually kept in the messages file
wimp_menu * CreateMenu (char * szMenu)
{
  wimp_menu                   *pcMenu;
  char *                      pcBuffer;
  char *                      nAt = 0;
  char *                      nAt2 = 0;
  int                         nWidth = 0;
  wimp_menu_flags             uFlags;
  char                        szMenuString[512];
  char *                      pcMenuString;
  int                         nMenuItems;
  char                        *szItem;
  int                         nMenuEntry;
  int                         nStringLen;

  nMenuItems = -1;
  szItem = szMenu;
  while (szItem)
  {
    nMenuItems++;
    szItem = strchr(szItem + 1, ',');
  }

  pcMenu = (wimp_menu*)malloc (wimp_SIZEOF_MENU(nMenuItems));
  pcBuffer = (char *)malloc (strlen(szMenu));

  nAt = strchr (szMenu, ',');
  if (!nAt)
  {
    nAt = (char *)strlen (szMenu);
  }
  else
  {
    nAt -= (int)szMenu;
  }
  strncpy (szMenuString, szMenu, (int)nAt);
  szMenuString[(int)nAt] = 0;

  if (strlen (szMenuString) >= 12)
  {
    strcpy (pcBuffer, szMenuString);
    pcMenu->title_data.indirected_text.text = pcBuffer;
    pcBuffer += strlen (szMenuString) + 1;
    uFlags = wimp_MENU_TITLE_INDIRECTED;
  }
  else
  {
    strncpy (pcMenu->title_data.text, szMenuString, 12);
    uFlags = 0u;
  }
  pcMenu->title_fg = wimp_COLOUR_BLACK;
  pcMenu->title_bg = wimp_COLOUR_LIGHT_GREY;
  pcMenu->work_fg = wimp_COLOUR_BLACK;
  pcMenu->work_bg = wimp_COLOUR_WHITE;

  pcMenu->height = 44;
  pcMenu->gap = 0;

  nMenuEntry = 0;
  do
  {
    pcMenu->entries[nMenuEntry].sub_menu = (wimp_menu*)-1;
    pcMenu->entries[nMenuEntry].icon_flags = wimp_ICON_TEXT
                   | wimp_ICON_FILLED
                   | (wimp_COLOUR_BLACK << wimp_ICON_FG_COLOUR_SHIFT);
    nAt2 = nAt + 1;

    nAt = strchr (szMenu + (int)nAt2, ',');
    if (!nAt)
    {
      nAt = (char *)strlen (szMenu) + 1;
    }
    else
    {
      nAt -= (int)szMenu;
    }
    strncpy (szMenuString, szMenu + (int)nAt2, (int)nAt - (int)nAt2);
    szMenuString[(int)nAt - (int)nAt2] = 0;

    pcMenuString = szMenuString;

    if (pcMenuString[0] == '+')
    {
      uFlags = wimp_MENU_TICKED;
      pcMenuString++;
    }
    if (pcMenuString[0] == '-')
    {
      uFlags |= wimp_MENU_SEPARATE;
      pcMenuString++;
    }
    if (pcMenuString[0] == '|')
    {
      uFlags |= wimp_MENU_WRITABLE;
      pcMenuString++;
    }
    if (pcMenuString[0] == '>')
    {
      pcMenuString++;
      if (gnMenuWins < (int)sizeof(gawhMenuWinHandle))
      {
        gawhMenuWinHandle[gnMenuWins] = LoadTemplate (pcMenuString);
        pcMenu->entries[nMenuEntry].sub_menu
                   = (wimp_menu*)gawhMenuWinHandle[gnMenuWins];

        gnMenuWins++;
      }
    }

    nStringLen = (int)strlen (pcMenuString);
    if (strlen (pcMenuString) >= 12)
    {
      pcMenu->entries[nMenuEntry].icon_flags |= wimp_ICON_INDIRECTED;
      pcMenu->entries[nMenuEntry].data.indirected_text.text = pcBuffer;
      pcMenu->entries[nMenuEntry].data.indirected_text.validation = NULL;
      pcMenu->entries[nMenuEntry].data.indirected_text.size = nStringLen + 1;
      strcpy (pcBuffer, pcMenuString);
      pcBuffer += nStringLen + 1;
    }
    else
    {
      strncpy (pcMenu->entries[nMenuEntry].data.text, pcMenuString, 12);
    }

    pcMenu->entries[nMenuEntry].menu_flags = uFlags;
    if (nStringLen > nWidth)
    {
      nWidth = nStringLen;
    }
    nMenuEntry++;
    uFlags = 0u;
  } while ((int)nAt < (int)strlen (szMenu));

  pcMenu->width = (nWidth + 1) * 16;
  pcMenu->entries[nMenuEntry - 1].menu_flags |= wimp_MENU_LAST;

  return pcMenu;
}

//////////////////////////////////////////////////////////////////
// Attach a submenu to a menu
void SetSubMenu (wimp_menu * pcMain, int nItem, wimp_menu * pcSub)
{
  pcMain->entries[nItem].sub_menu = pcSub;
}

//////////////////////////////////////////////////////////////////
// Set the greyed out status of a menu item
void SetMenuItemGreyness (bool boState, wimp_menu * pcMenu, int nItem)
{
  if (boState)
  {
    pcMenu->entries[nItem].icon_flags |= wimp_ICON_SHADED;
  }
  else
  {
    pcMenu->entries[nItem].icon_flags &= ~wimp_ICON_SHADED;
  }
}

//////////////////////////////////////////////////////////////////
// Set the ticked status of a menu item
void SetMenuItemTicked (bool boTicked, wimp_menu * pcMenu, int nItem)
{
  if (boTicked)
  {
    pcMenu->entries[nItem].menu_flags |= wimp_MENU_TICKED;
  }
  else
  {
    pcMenu->entries[nItem].menu_flags &= ~wimp_MENU_TICKED;
  }
}

//////////////////////////////////////////////////////////////////
// Get the text string from an icon
char * GetIconText (wimp_w whWindow, wimp_i ihIcon)
{
  wimp_icon_state             sIconState;
  char                        *szString;
  int                         nTerminate = 0;

  sIconState.w = whWindow;
  sIconState.i = ihIcon;
  xwimp_get_icon_state (& sIconState);

  if (sIconState.icon.flags & wimp_ICON_INDIRECTED)
  {
    szString = sIconState.icon.data.indirected_text.text;
    while (szString[nTerminate] >= 32)
    {
      nTerminate++;
    }
    szString[nTerminate] = 0;
  }
  else
  {
    szString = sIconState.icon.data.text;
    while ((szString[nTerminate] >= 32) && (nTerminate < 12))
    {
      nTerminate++;
    }
    szString[nTerminate] = 0;
  }
  return szString;
}

//////////////////////////////////////////////////////////////////
// Set the text string for an icon
void SetIconText (char * szText, wimp_w whWindow, wimp_i ihIcon)
{
  wimp_icon_state             sIconState;

  sIconState.w = whWindow;
  sIconState.i = ihIcon;
  xwimp_get_icon_state (& sIconState);

  if (sIconState.icon.flags & wimp_ICON_INDIRECTED)
  {
    strcpy (sIconState.icon.data.indirected_text.text, szText);
  }
  else
  {
    strncpy (sIconState.icon.data.text, szText, 12);
    sIconState.icon.data.text[11] = 0;
  }

  xwimp_set_icon_state (whWindow, ihIcon, 0, 0);
}

//////////////////////////////////////////////////////////////////
// Get the selection state from an icon
bool GetIconSelectionState (wimp_w whWindow, wimp_i ihIcon)
{
  wimp_icon_state             sIconState;
  bool                        boState;

  sIconState.w = whWindow;
  sIconState.i = ihIcon;
  xwimp_get_icon_state (& sIconState);

  if (sIconState.icon.flags & wimp_ICON_SELECTED)
  {
    boState = TRUE;
  }
  else
  {
    boState = FALSE;
  }

  return boState;
}

//////////////////////////////////////////////////////////////////
// Set the selection state from an icon
void SetIconSelectionState (bool boState, wimp_w whWindow, wimp_i ihIcon)
{
  wimp_icon_flags             uFlagsEOR;

  if (boState)
  {
    uFlagsEOR = wimp_ICON_SELECTED;
  }
  else
  {
    uFlagsEOR = 0;
  }

  xwimp_set_icon_state (whWindow, ihIcon, uFlagsEOR, wimp_ICON_SELECTED);
}

//////////////////////////////////////////////////////////////////
// Grey out or ungrey an icon
void SetIconGreyness (bool boState, wimp_w whWindow, wimp_i ihIcon)
{
  wimp_icon_flags             uFlagsEOR;

  if (boState)
  {
    uFlagsEOR = wimp_ICON_SHADED;
  }
  else
  {
    uFlagsEOR = 0;
  }

  xwimp_set_icon_state (whWindow, ihIcon, uFlagsEOR, wimp_ICON_SHADED);
}

//////////////////////////////////////////////////////////////////
// Sender wants to send data to the receiver
// Normal use: user has terminated a drag, so this message is sent
// Response:   DataSaveAck
//             RamFetch
void DataSave (wimp_block *pcBlock)
{
  // Check filetype
  if (pcBlock->message.data.data_xfer.file_type == COMPOSE_FILETYPE)
  {
    // Make a copy in case the RamFetch returns;
    gsMessageStore = pcBlock->message.data.data_xfer;
    gthSender = pcBlock->message.sender;

    pcBlock->message.size = 28;
    pcBlock->message.your_ref = pcBlock->message.my_ref;
    pcBlock->message.action = message_RAM_FETCH;
    pcBlock->message.data.ram_xfer.addr = gpcRamTransBuffer;
    pcBlock->message.data.ram_xfer.size = RAMTRANSMIT_SIZE;

    xwimp_send_message (wimp_USER_MESSAGE_RECORDED, & pcBlock->message,
      pcBlock->message.sender);

    gnMyRef = pcBlock->message.my_ref;
    geLoadType = LOADTYPE_FILE;
  }
}

//////////////////////////////////////////////////////////////////
// RamFetch Acknowledge received - try DataSaveAck instead
// Usually followed by a DataLoad message
void RamFetchReturned (wimp_block *pcBlock)
{
  if (gnMyRef == pcBlock->message.my_ref)
  {
    // Use the copy made earlier during the DataSave
    pcBlock->message.data.data_xfer = gsMessageStore;

    pcBlock->message.size = 60;
    pcBlock->message.your_ref = pcBlock->message.my_ref;
    pcBlock->message.action = message_DATA_SAVE_ACK;
    strcpy (pcBlock->message.data.data_xfer.file_name, "<Wimp$Scrap>");

    xwimp_send_message (wimp_USER_MESSAGE, & pcBlock->message,
      gthSender);
  }
}

//////////////////////////////////////////////////////////////////
// Acknowledge that you received a DataSave message
// Usually followed by a DataLoad message
void DataSaveAck (wimp_block *pcBlock)
{
  char                        *szFilename;
  int                         nStringLen;

  CloseMenu ();

  switch (geSaveType)
  {
      case SAVETYPE_FILE:
        szFilename = pcBlock->message.data.data_xfer.file_name;
        // Save the file to the given filename
        SetIconText (szFilename, gwhSave, 2);

        // Save the file
        SaveFile (szFilename);

        pcBlock->message.your_ref = pcBlock->message.my_ref;
        pcBlock->message.action = message_DATA_LOAD;

        xwimp_send_message (wimp_USER_MESSAGE, & pcBlock->message,
          pcBlock->message.sender);
        break;
      case SAVETYPE_ICONTEXT:
        szFilename = pcBlock->message.data.data_xfer.file_name;
        // Save the file to the given filename
        nStringLen = strlen (gszSaveString);

        err (xosfile_save_stamped (szFilename, 0xfff, gszSaveString,
          (gszSaveString + nStringLen)));

        pcBlock->message.your_ref = pcBlock->message.my_ref;
        pcBlock->message.action = message_DATA_LOAD;

        xwimp_send_message (wimp_USER_MESSAGE, & pcBlock->message,
          pcBlock->message.sender);
        break;
      default:
        break;
  }
}

//////////////////////////////////////////////////////////////////
// Receiver of this message should load the file
// and answer with DataSaveAck if successful
void DataLoad (wimp_block *pcBlock)
{
  char                        *szFilename;
  char                        *pcLoadedFile;
  int                         nSize;

  // Check filetype
  switch (pcBlock->message.data.data_xfer.file_type)
  {
    case COMPOSE_FILETYPE: // Text file
      szFilename = pcBlock->message.data.data_xfer.file_name;

      // Load the file from the given filename
      LoadFileFlex (szFilename, & pcLoadedFile, & nSize);

      // Delete the file
      if (strcmp (szFilename, "<Wimp$Scrap>") == 0)
      {
        err (xosfile_delete (szFilename, NULL, NULL, NULL, NULL, NULL));
      }

      if (pcLoadedFile)
      {
        pcBlock->message.your_ref = pcBlock->message.my_ref;
        pcBlock->message.action = message_DATA_LOAD_ACK;

        xwimp_send_message (wimp_USER_MESSAGE, & pcBlock->message,
          pcBlock->message.sender);

        strncpy (gszConfigFile, szFilename, sizeof (gszConfigFile));

        FileLoadedFlex (& pcLoadedFile, nSize);

        // Now we're done with the file
        if (pcLoadedFile)
        {
          flex_free ((flex_ptr)(& pcLoadedFile));
          pcLoadedFile = NULL;
        }
        SetIconText (szFilename, gwhSave, 2);
      }
      break;
    case 0x2000: // Application directory
      if (pcBlock->message.data.data_xfer.w == gwhMain)
      {
        LoadComponent (pcBlock->message.data.data_xfer.file_name,
          pcBlock->message.data.data_xfer.pos.x,
          pcBlock->message.data.data_xfer.pos.y);
      }
      break;
    default:
      // Do nothing
      break;
  }
}

//////////////////////////////////////////////////////////////////
// User has double clicked on a file
void DataOpen (wimp_block *pcBlock)
{
  char                        *szFilename;
  char                        *pcLoadedFile;
  int                         nSize;

  // Check filetype
  if (pcBlock->message.data.data_xfer.file_type == COMPOSE_FILETYPE)
  {
    szFilename = pcBlock->message.data.data_xfer.file_name;

    // Load the file from the given filename
    LoadFileFlex (szFilename, & pcLoadedFile, & nSize);

    if (pcLoadedFile)
    {
      pcBlock->message.your_ref = pcBlock->message.my_ref;
      pcBlock->message.action = message_DATA_LOAD_ACK;

      xwimp_send_message (wimp_USER_MESSAGE, & pcBlock->message,
        pcBlock->message.sender);

      strncpy (gszConfigFile, szFilename, sizeof (gszConfigFile));
      FileLoadedFlex (& pcLoadedFile, nSize);

      // Now we're done with the file
      if (pcLoadedFile)
      {
        flex_free ((flex_ptr)(& pcLoadedFile));
      }
      pcLoadedFile = NULL;
      SetIconText (szFilename, gwhSave, 2);
      OpenWindowInitPane (gwhMain, gwhMaHe, PANE_TOP);
    }
  }
}

//////////////////////////////////////////////////////////////////
// Copy the data from here to the external program
// Reply with RamTransmit
void RamFetch (wimp_block *pcBlock)
{
  wimp_t                      nDestHandle;
  int                         nSize;
  int                         nSendLen;

  nSize = pcBlock->message.data.ram_xfer.size;
  nDestHandle = pcBlock->message.sender;

  if (gnRamTransRef != pcBlock->message.your_ref)
  {
    // First in a series of RAM transfers
    CloseMenu ();

    switch (geSaveType)
    {
      case SAVETYPE_FILE:
        // Save out the structure file
        gpsStructureMemFile = CreateStructureFile ();
        gpcRamTransPos = * meminfo (gpsStructureMemFile, & gnRamTransLeft);
        gnRamTransRef = pcBlock->message.your_ref;
        break;
      case SAVETYPE_ICONTEXT:
        gpcRamTransPos = gszSaveString;
        gnRamTransLeft = (int)strlen (gszSaveString);
        gnRamTransRef = pcBlock->message.your_ref;
        break;
      default:
        gnRamTransRef = -1;
        break;
    }
  }

  if ((gnRamTransRef == pcBlock->message.your_ref) && (gpcRamTransPos))
  {
    // Continue with a previously started RAM transfer
    // (it might have been started just a few lines of code before this!)
    if (gnRamTransLeft < nSize)
    {
      // This will be the last transfer
      nSendLen = gnRamTransLeft;
    }
    else
    {
      // There will be more to transfer after this
      nSendLen = nSize;
    }

    if (nSendLen > 0)
    {
      xwimp_transfer_block (gnTaskHandle, gpcRamTransPos, nDestHandle,
        pcBlock->message.data.ram_xfer.addr, nSendLen);
    }

    pcBlock->message.size = 28;
    pcBlock->message.your_ref = pcBlock->message.my_ref;
    pcBlock->message.action = message_RAM_TRANSMIT;
    pcBlock->message.data.ram_xfer.size = nSendLen;

    xwimp_send_message (wimp_USER_MESSAGE, & pcBlock->message,
      pcBlock->message.sender);

    gpcRamTransPos += nSendLen;
    gnRamTransLeft -= nSendLen;

    if (nSendLen < nSize)
    {
      gnRamTransRef = -1;
      if (geSaveType == SAVETYPE_FILE)
      {
        memclose (gpsStructureMemFile);
      }
    }
    else
    {
      gnRamTransRef = pcBlock->message.my_ref;
    }
  }
}

//////////////////////////////////////////////////////////////////
// Response to a RamFetch
void RamTransmit (wimp_block *pcBlock)
{
  char *                      pcLoadedFile;
  int                         nSize;
  int                         nSuccess = 1;

  if (gnMyRef == pcBlock->message.your_ref)
  {
    pcLoadedFile = pcBlock->message.data.ram_xfer.addr;
    nSize = pcBlock->message.data.ram_xfer.size;

    if (nSize != 0)
    {
      switch (geLoadType)
      {
        case LOADTYPE_FILE:
          if (gpcLoadedFile == NULL)
          {
            // The first transfer
            // Create some flex memory
            nSuccess = flex_alloc ((flex_ptr)(& gpcLoadedFile), nSize);
            if (nSuccess == 1)
            {
              // Copy the data
              memcpy (gpcLoadedFile, pcLoadedFile, nSize);
              gnLoadedFileSize = nSize;
            }
          }
          else
          {
            // A continuation of the transfer
            // Extend the flex memory
            nSuccess = flex_extend ((flex_ptr)(& gpcLoadedFile),
              gnLoadedFileSize + nSize);
            if (nSuccess == 1)
            {
              // Copy the data
              memcpy (gpcLoadedFile + gnLoadedFileSize, pcLoadedFile, nSize);
              gnLoadedFileSize += nSize;
            }
            else
            {
              // Something went wrong, so we'd better just free up the
              // memory
              flex_free ((flex_ptr)(& gpcLoadedFile));
              gpcLoadedFile = NULL;
              gnLoadedFileSize = 0;
            }
          }

          if ((nSize < RAMTRANSMIT_SIZE) && (nSuccess == 1))
          {
            // The last transfer
            // Save the file out, for the sake of the example
            gszConfigFile[0] = 0;
            FileLoadedFlex (& gpcLoadedFile, gnLoadedFileSize);

            // Now we're done with the file
            if (gpcLoadedFile)
            {
              flex_free ((flex_ptr)(& gpcLoadedFile));
            }
            gpcLoadedFile = NULL;
            gnLoadedFileSize = 0;
          }
        default:
          break;
      }
    }

    if (nSuccess == 1)
    {
      if (nSize == RAMTRANSMIT_SIZE)
      {
        // The buffer was full, so we must ask for some more
        pcBlock->message.size = 28;
        pcBlock->message.your_ref = pcBlock->message.my_ref;
        pcBlock->message.action = message_RAM_FETCH;
        pcBlock->message.data.ram_xfer.addr = gpcRamTransBuffer;
        pcBlock->message.data.ram_xfer.size = RAMTRANSMIT_SIZE;

        xwimp_send_message (wimp_USER_MESSAGE, & pcBlock->message,
          pcBlock->message.sender);

        gnMyRef = pcBlock->message.my_ref;
      }
      else
      {
        // This was the final transfer
        geLoadType = LOADTYPE_INVALID;
      }
    }
    else
    {
      // There was a problem allocating memory
      ShowWarningTag ("Er2");
      geLoadType = LOADTYPE_INVALID;
    }
  }
}

//////////////////////////////////////////////////////////////////
// Response to a Help message
void Help (wimp_block *pcBlock)
{
  wimp_w                      whWindow;
  wimp_i                      ihIcon;
  char                        szHelpText[236];
  int                         nStrLen;
  char                        szTag[256];
  int                         nWinNum = 0;
  wimp_selection              sSelection;
  bool                        boGeneral;

  whWindow = *(wimp_w*)(pcBlock->message.data.reserved + 12);
  ihIcon = *(wimp_i*)(pcBlock->message.data.reserved + 16);

   // Icon bar
  if (whWindow == wimp_ICON_BAR)
  {
    nWinNum = 1;
  }

  // Main window
  if (whWindow == gwhMain)
  {
    nWinNum = 2;
  }

  // Warning window
  if (whWindow == gwhWarn)
  {
    nWinNum = 3;
  }

  // Information window
  if (whWindow == gawhMenuWinHandle[0])
  {
    nWinNum = 4;
  }

  // Save window
  if (whWindow == gwhSave)
  {
    nWinNum = 5;
  }

  if (nWinNum)
  {
    // Window
    szHelpText[0] = 0;
    sprintf (szTag, "H%dI%d", nWinNum, ihIcon);
    strncpy (szHelpText, TagCheck (szTag), sizeof(szHelpText));
    nStrLen = strlen(szHelpText);
    if (nStrLen == 0)
    {
      sprintf (szTag, "H%d", nWinNum);
      strncpy (szHelpText, TagCheck (szTag), sizeof(szHelpText));
    }
    szHelpText[sizeof(szHelpText) - 1] = 0;
    nWinNum = -1;
  }
  else
  {
    // Menu
    wimp_get_menu_state (wimp_GIVEN_WINDOW_AND_ICON, & sSelection,
      whWindow, ihIcon);

    boGeneral = FALSE;
    if (sSelection.items[0] != -1)
    {
      // Iconbar menu
      if (gpcMenuCurrent == gpcIconBarMenu)
      {
        nWinNum = 6;
      }

      // Main window menu
      if (gpcMenuCurrent == gpcWindowMenu)
      {
        nWinNum = 7;
      }
    }
    if (nWinNum)
    {
      if (boGeneral)
      {
        sprintf (szTag, "H%d", nWinNum);
      }
      else
      {
        if (sSelection.items[1] != -1)
        {
          sprintf (szTag, "H%dI%dI%d", nWinNum, sSelection.items[0],
            sSelection.items[1]);
        }
        else
        {
          sprintf (szTag, "H%dI%d", nWinNum, sSelection.items[0]);
        }
      }

      strncpy (szHelpText, TagCheck (szTag), sizeof(szHelpText));
      nWinNum = -1;
    }
  }

  if (nWinNum == -1)
  {
    pcBlock->message.size = WORDALIGN((24 + strlen(szHelpText)));
    pcBlock->message.your_ref = pcBlock->message.my_ref;
    pcBlock->message.action = 0x503;
    strncpy (pcBlock->message.data.reserved, szHelpText, sizeof(szHelpText));

    xwimp_send_message (wimp_USER_MESSAGE, & pcBlock->message,
      pcBlock->message.sender);
  }
}

//////////////////////////////////////////////////////////////////
// Obtain string from Messages file
char * Tag (char * szTag)
{
  messagetrans_lookup ((messagetrans_control_block*)gpcMessages,
    szTag, gpcTemp, 256, 0, 0, 0, 0, NULL);

  return gpcTemp;
}

//////////////////////////////////////////////////////////////////
// Obtain string from Messages file if it exists
char * TagCheck (char * szTag)
{
  osbool                      boMore = FALSE;
  int                         nUsed = 0;
  char                        *szResult;
  os_error                    *psError;

  xmessagetrans_enumerate_tokens ((messagetrans_control_block*)gpcMessages,
    szTag, gpcTemp, 256, 0, & boMore, & nUsed, NULL);

  if (!boMore)
  {
    strcpy (gpcTemp, "");
    szResult = gpcTemp;
  }
  else
  {
    psError = xmessagetrans_lookup ((messagetrans_control_block*)gpcMessages,
      szTag, gpcTemp, 256, NULL, NULL, NULL, NULL, & szResult, & nUsed);

    if (psError)
    {
      strcpy (gpcTemp, "");
      szResult = gpcTemp;
    }
  }

  return szResult;
}

//////////////////////////////////////////////////////////////////
// Load a file into a block of memory
char * LoadFile (char * szFilename, int * pnSize)
{
  char                        *pcMemory;
  int                         nSize;
  os_error                    *psError;
  fileswitch_object_type      eObjectType;

  xosfile_read_stamped_no_path (szFilename, & eObjectType, NULL, NULL,
    & nSize, NULL, NULL);

  if ((eObjectType != fileswitch_NOT_FOUND) && (nSize > 0))
  {
    if (pnSize)
    {
      *pnSize = nSize;
    }
    pcMemory = malloc (nSize);

    if (pcMemory)
    {
      psError = xosfile_load_stamped_no_path (szFilename, pcMemory, NULL,
        NULL, NULL, NULL, NULL);

      if (psError)
      {
        free (pcMemory);
        pcMemory = NULL;
        err (psError);
      }
    }
    else
    {
      ShowWarningTag ("Er3");
    }
  }
  else
  {
    pcMemory = NULL;
  }

  return pcMemory;
}

////////////////////////////////////////////////////////////////////
//// Load a file into a block of flex memory
//int LoadFileFlex (char * szFilename, char * * ppcMemory, int * pnSize)
//{
//  int                         nSize;
//  int                         nSuccess = 0;
//  fileswitch_object_type      eObjectType;
//  os_error                    *psError;
//
//  xosfile_read_stamped_no_path (szFilename, & eObjectType, NULL, NULL,
//    & nSize, NULL, NULL);
//
//  if ((eObjectType != fileswitch_NOT_FOUND) && (nSize > 0))
//  {
//    if (pnSize)
//    {
//      *pnSize = nSize;
//    }
//    nSuccess = flex_alloc ((flex_ptr) ppcMemory, nSize);
//
//    if ((nSuccess == 1) && (*ppcMemory))
//    {
//      psError = xosfile_load_stamped_no_path (szFilename, * ppcMemory, NULL,
//        NULL, NULL, NULL, NULL);
//
//      if (psError)
//      {
//        flex_free ((flex_ptr) ppcMemory);
//        *ppcMemory = NULL;
//        nSuccess = -1;
//        err (psError);
//      }
//    }
//    else
//    {
//      ShowWarningTag ("Er3");
//      nSuccess = 0;
//      *ppcMemory = NULL;
//    }
//  }
//  else
//  {
//    nSuccess = -1;
//    *ppcMemory = NULL;
//  }
//
//  return nSuccess;
//}

//////////////////////////////////////////////////////////////////
// Load a sprite file into a block of memory
osspriteop_area * LoadSprites (char * szFilename)
{
  osspriteop_area             *pcMemory;
  int                         nSize;

  xosfile_read_stamped_no_path (szFilename, NULL, NULL, NULL, & nSize,
    NULL, NULL);

  if (nSize > 0)
  {
    nSize += 4;
    pcMemory = (osspriteop_area*)malloc (nSize);

    if (pcMemory)
    {
      pcMemory->size = nSize;
      pcMemory->sprite_count = 0;
      pcMemory->first = 16;
      pcMemory->used = 16;
      xosspriteop_clear_sprites (osspriteop_USER_AREA, pcMemory);
      err (xosspriteop_load_sprite_file (osspriteop_USER_AREA, pcMemory,
        szFilename));
    }
  }
  else
  {
    pcMemory = NULL;
  }

  return pcMemory;
}

//////////////////////////////////////////////////////////////////
// Display an error from the Messages file in a warning box
void ShowWarningTag (char * szTag)
{
    xos_bell  ();
    SetIconText (Tag(szTag), gwhWarn, 1);
    OpenWindowInitCentre (gwhWarn);
    CloseMenu ();

    xwimp_set_caret_position (gwhWarn, wimp_ICON_WINDOW, 0, 0, -1, -1);
}

//////////////////////////////////////////////////////////////////
// Display an error in a warning box
void ShowWarning (os_error * sError)
{
    xos_bell  ();
    SetIconText (sError->errmess, gwhWarn, 1);
    OpenWindowInitCentre (gwhWarn);
    CloseMenu ();

    xwimp_set_caret_position (gwhWarn, wimp_ICON_WINDOW, 0, 0, -1, -1);
}

//////////////////////////////////////////////////////////////////
// Close the warning message box
void CloseWarning (void)
{
  CloseWindowHandle (gwhWarn);
}

//////////////////////////////////////////////////////////////////
// Initiate a sprite drag from the contents of the given icon
void DragSprite (wimp_w whWindow, wimp_i ihIcon, char * szIconName)
{
  osspriteop_area             *pcSpriteArea;
  int                         nXinc;
  int                         nYinc;
  wimp_icon_state             sIconState;
  wimp_window_info            sWindowInfo;

  if (!gboDrag)
  {
    sIconState.w = whWindow;
    sIconState.i = ihIcon;
    xwimp_get_icon_state (& sIconState);

    if (((sIconState.icon.flags & (wimp_ICON_TEXT
      | wimp_ICON_SPRITE | wimp_ICON_INDIRECTED))
      == (wimp_ICON_SPRITE | wimp_ICON_INDIRECTED)))
    {
      pcSpriteArea = sIconState.icon.data.indirected_sprite.area;
    }
    else
    {
      pcSpriteArea = NULL;
    }

    sWindowInfo.w = whWindow;
    xwimp_get_window_info_header_only (& sWindowInfo);

    if (!pcSpriteArea)
    {
      pcSpriteArea = sWindowInfo.sprite_area;
    }

    nXinc = sWindowInfo.visible.x0 - sWindowInfo.xscroll;
    nYinc = sWindowInfo.visible.y1 - sWindowInfo.yscroll;

    sIconState.icon.extent.x0 += nXinc;
    sIconState.icon.extent.y0 += nYinc;
    sIconState.icon.extent.x1 += nXinc;
    sIconState.icon.extent.y1 += nYinc;

    xdragasprite_start ((dragasprite_HPOS_CENTRE | dragasprite_VPOS_CENTRE
      | dragasprite_BOUND_POINTER | dragasprite_DROP_SHADOW),
      pcSpriteArea, szIconName, & sIconState.icon.extent, NULL);

    gboDrag = TRUE;
  }
}

//////////////////////////////////////////////////////////////////
// Initiate a outline drag from the dimensions of the given icon
void DragBox (wimp_w whWindow, wimp_i ihIcon)
{
  int                         nXinc;
  int                         nYinc;
  wimp_icon_state             sIconState;
  wimp_window_state           sWindowState;
  wimp_drag                   sDragInfo;

  if (!gboDrag)
  {
    sIconState.w = whWindow;
    sIconState.i = ihIcon;
    xwimp_get_icon_state (& sIconState);

    sWindowState.w = whWindow;
    xwimp_get_window_state (& sWindowState);

    nXinc = sWindowState.visible.x0 - sWindowState.xscroll;
    nYinc = sWindowState.visible.y1 - sWindowState.yscroll;

    sDragInfo.w = 0;
    sDragInfo.type = wimp_DRAG_USER_FIXED;
    sDragInfo.initial.x0 = sIconState.icon.extent.x0 + nXinc;
    sDragInfo.initial.y0 = sIconState.icon.extent.y0 + nYinc;
    sDragInfo.initial.x1 = sIconState.icon.extent.x1 + nXinc;
    sDragInfo.initial.y1 = sIconState.icon.extent.y1 + nYinc;
    sDragInfo.bbox.x0 = 0;
    sDragInfo.bbox.y0 = 0;
    sDragInfo.bbox.x1 = 0;
    sDragInfo.bbox.y1 = 0;
    sDragInfo.handle = NULL;
    sDragInfo.draw = NULL;
    sDragInfo.undraw = NULL;
    sDragInfo.redraw = NULL;

    xwimp_drag_box (& sDragInfo);

    gboDrag = TRUE;
  }
}

//////////////////////////////////////////////////////////////////
// Initiate a URL link
void OpenURL (char * szURL)
{
  wimp_message                sMessage;

  sMessage.your_ref = 0;
  sMessage.action = 0x4af80;

  strcpy ((char *)sMessage.data.reserved, szURL);

  sMessage.size = WORDALIGN(strlen(szURL) + 21);

  xwimp_send_message (wimp_USER_MESSAGE_RECORDED, & sMessage,
    wimp_BROADCAST);

  gnMessageMyRef = sMessage.my_ref;
}

//////////////////////////////////////////////////////////////////
// No URL lancher responded, so we have to run one ourselves
void OpenURLReturned (wimp_block *pcBlock)
{
  char                        szAlias[255];
  char                        szVar[255];
  int                         nUsed;

  if (gnMessageMyRef == pcBlock->message.my_ref)
  {
    if (strncmp (pcBlock->message.data.reserved, "mailto:", 7) == 0)
    {
      strcpy (szAlias, "URLOpen_MailTo");
    }
    else
    {
      strcpy (szAlias, "URLOpen_HTTP");
    }

    sprintf (szVar, "Alias$%s", szAlias);
    xos_read_var_val (szVar, 0, -1, 0, os_VARTYPE_STRING, & nUsed,
      NULL, NULL);

    if (nUsed)
    {
      sprintf (szVar, "%s %s", szAlias, pcBlock->message.data.reserved);
      err (xwimp_start_task (szVar, NULL));
    }
  }
}

// Save box save button clicked
void SaveSave (void)
{
  char                        *szFilename;

  szFilename = GetIconText (gwhSave, 2);

  if (strchr(szFilename, '.'))
  {
    // Save the file
    SaveFile (szFilename);
  }
  else
  {
    ShowWarningTag ("Er1");
  }
}

#if defined _DEBUG
//////////////////////////////////////////////////////////////////
// Report a debug message
void Report (char * szMessage)
{
  _kernel_swi_regs            sRegs;

  sRegs.r[0] = (int)szMessage;
  _kernel_swi (Report_Text0, & sRegs, & sRegs);
}

//////////////////////////////////////////////////////////////////
// Report a debug message with variable
void ReportVar (char * szFormat, int nVariable)
{
  _kernel_swi_regs            sRegs;
  char                        szReport[255];

  sprintf (szReport, szFormat, nVariable);
  sRegs.r[0] = (int)szReport;
  _kernel_swi (Report_Text0, & sRegs, & sRegs);
}
#endif // _DEBUG

//////////////////////////////////////////////////////////////////
// File loaded into flex memory routine
void FileLoadedFlex (char * * pcMemory, int nSize)
{
  MemFile                     *psStructureFile = NULL;

  // Turn it into a MemFile
  psStructureFile = memcreate (pcMemory, nSize, "r");
  LoadStructureFile (psStructureFile);
  memclose (psStructureFile);
  *pcMemory = NULL;

//  // Save the file out, for the sake of the example
//  err (xosfile_save_stamped (GetIconText (gwhSave, 2), 0xffd, *pcMemory,
//    (*pcMemory + nSize)));
}

//////////////////////////////////////////////////////////////////
// Save file routine - saves the example file out
void SaveFile (char * szFilename)
{
  MemFile                     *psMemFile = NULL;
  char                        **ppcFile;
  int                         nSize;
  int                         nComponent;
  wimp_message                sMessage;

  psMemFile = CreateStructureFile ();
  ppcFile = meminfo (psMemFile, & nSize);

  err (xosfile_save_stamped (szFilename, gnSaveFileType, *ppcFile,
    (*ppcFile + nSize)));

  memclose (psMemFile);

  gnConfigNum = gnComponents;

  // Save configuration data from any components
  // Send "config save" message to all of the components
  for (nComponent = 0 ; nComponent < gnComponents; nComponent++)
  {
    if (gasComponents[nComponent].hTask != NULL)
    {
      ((int *)(sMessage.data.reserved))[0] = nComponent;
      ((int *)(sMessage.data.reserved))[1] = 2; // Save config
      strncpy (sMessage.data.reserved + 8, szFilename, 236 - 8);
      sMessage.data.reserved[236 - 8] = 0;

      sMessage.size = WORDALIGN ((28 + strlen (sMessage.data.reserved + 8)
        + 1));
      sMessage.your_ref = 0;
      sMessage.action = message_LINK_CONTROL;

      xwimp_send_message (wimp_USER_MESSAGE, & sMessage,
        gasComponents[nComponent].hTask);

      REPORTVAR ("SEND: LinkControl handle = %d", nComponent);
      REPORTVAR ("      LinkControl task = %d",
        (int)gasComponents[nComponent].hTask);
      REPORTVAR ("      LinkControl action = %d", 0);
    }
    else
    {
      if (gnConfigNum > (nComponent + 1))
      {
        gnConfigNum = nComponent + 1;
      }
    }
  }

  strncpy (gszConfigFile, szFilename, sizeof (gszConfigFile));
}

//////////////////////////////////////////////////////////////////
// Poll 1: Redraw window
void RedrawWindow (wimp_block *pcBlock)
{
  bool                        boMore;
  wimp_w                      whWindow;

  err (xwimp_redraw_window (& pcBlock->redraw, & boMore));
  whWindow = pcBlock->redraw.w;

  if (whWindow == gwhMain)
  {
    while (boMore)
    {
      // Redraw the main window
      RedrawMain (& pcBlock->redraw);
      err (xwimp_get_rectangle (& pcBlock->redraw, & boMore));
    }
  }
}

//////////////////////////////////////////////////////////////////
// Redraw the main window
void RedrawMain (wimp_draw *psRedraw)
{
  int                         nVisMinX = psRedraw->box.x0;
//  int                         nVisMinY = psRedraw->box.y0;
//  int                         nVisMaxX = psRedraw->box.x1;
  int                         nVisMaxY = psRedraw->box.y1;

//  int                         nRedMinX = psRedraw->clip.x0;
//  int                         nRedMinY = psRedraw->clip.y0;
//  int                         nRedMaxX = psRedraw->clip.x1;
//  int                         nRedMaxY = psRedraw->clip.y1;

  int                         nScrollX = psRedraw->xscroll;
  int                         nScrollY = psRedraw->yscroll;

  int                         nComponent;
  int                         nXPos;
  int                         nYPos;

  int                         nLink;
  int                         nLinkXStart;
  int                         nLinkYStart;
  int                         nLinkXEnd;
  int                         nLinkYEnd;

  psRedraw = psRedraw;

  // Draw the components
  for (nComponent = 0; nComponent < gnComponents; nComponent++)
  {
    // Plot the component
    nXPos = nVisMinX - nScrollX + gasComponents[nComponent].nXPos;
    nYPos = nVisMaxY - nScrollY + gasComponents[nComponent].nYPos;

    if (!gasComponents[nComponent].boHidden)
    {
      osspriteop_put_sprite_scaled (osspriteop_PTR,
        gasComponents[nComponent].pcSprites,
        (osspriteop_id)((char*)gasComponents[nComponent].pcSprites
        + gasComponents[nComponent].pcSprites->first),
        nXPos, nYPos,
        osspriteop_USE_MASK, 0, gasComponents[nComponent].psTransTable);

      // Plot the outward link stubs
      for (nLink = 0; nLink < gasComponents[nComponent].nMaxLinksOut;
        nLink++)
      {
        nLinkXStart = gasComponents[nComponent].nXPos
          + gasComponents[nComponent].nWidth - LINK_STUB_WIDTH;
        nLinkYStart = gasComponents[nComponent].nYPos
          + (((float)(nLink)
          + 0.5f)
          * (float)gasComponents[nComponent].nHeight
          / (float)(gasComponents[nComponent].nMaxLinksOut));
        nLinkXEnd = gasComponents[nComponent].nXPos
          + gasComponents[nComponent].nWidth;
        nLinkYEnd = nLinkYStart;

        xcolourtrans_set_gcol (LINKSTUB_COLOUR, 0, os_ACTION_OVERWRITE,
          NULL, NULL);
        PlotStubOut (nVisMinX - nScrollX + nLinkXStart,
          nVisMaxY - nScrollY + nLinkYStart,
          nVisMinX - nScrollX + nLinkXEnd,
          nVisMaxY - nScrollY + nLinkYEnd);
      }

      // Plot the inward link stubs
      for (nLink = 0; nLink < gasComponents[nComponent].nMaxLinksIn;
        nLink++)
      {
        nLinkXStart = gasComponents[nComponent].nXPos;
        nLinkYStart = gasComponents[nComponent].nYPos
          + (((float)(nLink)
          + 0.5f)
          * (float)gasComponents[nComponent].nHeight
          / (float)(gasComponents[nComponent].nMaxLinksIn));
        nLinkXEnd = nLinkXStart + LINK_STUB_WIDTH;
        nLinkYEnd = nLinkYStart;

        xcolourtrans_set_gcol (LINKSTUB_COLOUR, 0, os_ACTION_OVERWRITE,
          NULL, NULL);
        PlotStubIn (nVisMinX - nScrollX + nLinkXStart,
          nVisMaxY - nScrollY + nLinkYStart,
          nVisMinX - nScrollX + nLinkXEnd,
          nVisMaxY - nScrollY + nLinkYEnd);
      }
    }
  }

  // Now draw the links
  for (nComponent = 0; nComponent < gnComponents; nComponent++)
  {
    for (nLink = 0; nLink < gasComponents[nComponent].nLinksOut; nLink++)
    {
      nLinkXStart = gasComponents[nComponent].nXPos
        + gasComponents[nComponent].nWidth;
      nLinkYStart = gasComponents[nComponent].nYPos
        + (((float)(gasComponents[nComponent].asLinksOut[nLink].nLinkOut)
        + 0.5f)
        * (float)gasComponents[nComponent].nHeight
        / (float)(gasComponents[nComponent].nMaxLinksOut));
      nLinkXEnd = gasComponents[gasComponents[nComponent]
        .asLinksOut[nLink].nComponentTo].nXPos;
      nLinkYEnd = gasComponents[gasComponents[nComponent]
        .asLinksOut[nLink].nComponentTo].nYPos
        + (((float)(gasComponents[nComponent].asLinksOut[nLink].nLinkIn)
        + 0.5f)
        * (float)gasComponents[gasComponents[nComponent]
        .asLinksOut[nLink].nComponentTo].nHeight
        / (float)(gasComponents[gasComponents[nComponent]
        .asLinksOut[nLink].nComponentTo].nMaxLinksIn));

      xcolourtrans_set_gcol (LINK_COLOUR, 0, os_ACTION_OVERWRITE,
        NULL, NULL);
      PlotArrow (nVisMinX - nScrollX + nLinkXStart,
        nVisMaxY - nScrollY + nLinkYStart,
        nVisMinX - nScrollX + nLinkXEnd,
        nVisMaxY - nScrollY + nLinkYEnd);
    }
  }

  if (geSaveType == SAVETYPE_LINKEND)
  {
    xcolourtrans_set_gcol (DRAG_ARROW_COLOUR, 0, os_ACTION_EXCLUSIVE_DISJOIN,
      NULL, NULL);
    PlotArrow (nVisMinX - nScrollX + gnDragLinkXStart,
      nVisMaxY - nScrollY + gnDragLinkYStart,
      nVisMinX - nScrollX + gnDragLinkXEnd,
      nVisMaxY - nScrollY + gnDragLinkYEnd);
  }
}

//////////////////////////////////////////////////////////////////
// Response to a Mode Change
void ModeChange (void)
{
  GenerateFullTransTable ();
}

//////////////////////////////////////////////////////////////////
// Generate sprite palette translation table
void GenerateFullTransTable (void)
{
  int                         nComponent;

  for (nComponent = 0; nComponent < gnComponents; nComponent++)
  {
    GenerateTransTable (&gasComponents[nComponent].psTransTable,
      gasComponents[nComponent].pcSprites);
  }
}

//////////////////////////////////////////////////////////////////
// Generate sprite palette translation table
void GenerateTransTable (osspriteop_trans_tab **ppsTransTable, osspriteop_area *pcSprites)
{
  int                         nSize;

  if (*ppsTransTable)
  {
    free (*ppsTransTable);
    *ppsTransTable = NULL;
  }

  err (xcolourtrans_generate_table_for_sprite (pcSprites,
    (osspriteop_id)((char*)pcSprites + pcSprites->first),
    colourtrans_CURRENT_MODE, colourtrans_CURRENT_PALETTE,
    NULL, colourtrans_GIVEN_SPRITE,
    NULL, NULL, & nSize));

  *ppsTransTable = (osspriteop_trans_tab*)malloc (nSize);

  err (xcolourtrans_generate_table_for_sprite (pcSprites,
    (osspriteop_id)((char*)pcSprites + pcSprites->first),
    os_CURRENT_MODE, colourtrans_CURRENT_PALETTE,
    *ppsTransTable, colourtrans_GIVEN_SPRITE,
    NULL, NULL, NULL));
}

//////////////////////////////////////////////////////////////////
// Reset the details of a component
void ResetComponent (Component * psComponent)
{
  int                         nCount;

  psComponent->nXPos = 0;
  psComponent->nYPos = 0;
  psComponent->nWidth = 0;
  psComponent->nHeight = 0;
  psComponent->pcSprites = NULL;
  psComponent->psTransTable = NULL;
  psComponent->boHidden = FALSE;
  psComponent->nMaxLinksOut = 0;
  psComponent->nMaxLinksIn = 0;
  psComponent->nLinksOut = 0;
  psComponent->nLinksIn = 0;
  psComponent->szDir[0] = 0;
  psComponent->hTask = NULL;
  psComponent->pcInfo = NULL;

  for (nCount = 0; nCount < INFO_MAX; nCount++)
  {
    psComponent->anInfoOffset[nCount] = 0;
  }
}

//////////////////////////////////////////////////////////////////
// Release the details of a component, freeing any used memory
void ReleaseComponent (Component * psComponent)
{
  int                         nCount;

  psComponent->nXPos = 0;
  psComponent->nYPos = 0;
  psComponent->nWidth = 0;
  psComponent->nHeight = 0;
  if (psComponent->pcSprites)
  {
    free (psComponent->pcSprites);
    psComponent->pcSprites = NULL;
  }
  if (psComponent->psTransTable)
  {
    free (psComponent->psTransTable);
    psComponent->psTransTable = NULL;
  }
  psComponent->boHidden = FALSE;
  psComponent->nMaxLinksOut = 0;
  psComponent->nMaxLinksIn = 0;
  psComponent->nLinksOut = 0;
  psComponent->nLinksIn = 0;
  psComponent->szDir[0] = 0;
  psComponent->hTask = NULL;

  if (psComponent->pcInfo)
  {
    free (psComponent->pcInfo);
    psComponent->pcInfo = NULL;
  }

  for (nCount = 0; nCount < INFO_MAX; nCount++)
  {
    psComponent->anInfoOffset[nCount] = 0;
  }
}

//////////////////////////////////////////////////////////////////
// Reset the details of a link out
void ResetLinkOut (LinkOut * psLink)
{
  psLink->nComponentTo = -1;
  psLink->nLinkOut = -1;
  psLink->nLinkIn = -1;
  psLink->pcData = NULL;
  psLink->nDataSize = 0;
  psLink->nTransBuffer = -1;
  psLink->nTransRef = -1;
  psLink->nSearchLinkIn = -1;
}

//////////////////////////////////////////////////////////////////
// Reset the details of a link out
void ResetLinkIn (LinkIn * psLink)
{
  psLink->nComponentFrom = -1;
  psLink->nLinkOut = -1;
  psLink->nLinkIn = -1;
  psLink->nDataSent = 0;
  psLink->nTransRef = -1;
  psLink->nSearchLinkOut = -1;
}

//////////////////////////////////////////////////////////////////
// Release the details of a link out, freeing any used memory
void ReleaseLinkOut (LinkOut * psLink)
{
  psLink->nComponentTo = -1;
  psLink->nLinkOut = -1;
  psLink->nLinkIn = -1;
  if (psLink->pcData)
  {
    flex_free ((flex_ptr)(& psLink->pcData));
  }
  psLink->pcData = NULL;
  psLink->nDataSize = 0;
  psLink->nTransBuffer = -1;
  psLink->nTransRef = -1;
  psLink->nSearchLinkIn = -1;
}

//////////////////////////////////////////////////////////////////
// Release the details of a link out, freeing any used memory
void ReleaseLinkIn (LinkIn * psLink)
{
  psLink->nComponentFrom = -1;
  psLink->nLinkOut = -1;
  psLink->nLinkIn = -1;
  psLink->nDataSent = 0;
  psLink->nTransRef = -1;
  psLink->nSearchLinkOut = -1;
}

//////////////////////////////////////////////////////////////////
// Mouse click in the main window
void MouseClickMain (int nXPos, int nYPos, wimp_mouse_state nButton)
{
//  int                         nXPrev;
//  int                         nYPrev;
  int                         nComponent;
  int                         nXRel;
  int                         nYRel;
  bool                        boFound;
  int                         nLinks;
  bool                        boLinkFound;
  int                         nLinkIn;
  int                         nLinkOut;

  // Is this a drag
  if (nButton & (wimp_DRAG_SELECT | wimp_DRAG_ADJUST))
  {
    // Check if we are inside a component
    boFound = FALSE;
    nComponent = 0;
    while ((nComponent < gnComponents) && !boFound)
    {
      nXRel = nXPos - gasComponents[nComponent].nXPos;
      nYRel = nYPos - gasComponents[nComponent].nYPos;
      if ((nXRel > 0) && (nXRel < gasComponents[nComponent].nWidth)
        && (nYRel > 0) && (nYRel < gasComponents[nComponent].nHeight))
      {
        // Bingo! We've hit a component!
        boFound = TRUE;
      }
      else
      {
        nComponent++;
      }
    }
    if (boFound)
    {
      gnComponentDrag = nComponent;

      // Are we moving a component, or a link?
      if (nXRel < LINKEND_WIDTH)
      {
        // Link end
        nLinkIn = (int)(((nYPos - gasComponents[nComponent].nYPos)
          * gasComponents[nComponent].nMaxLinksIn)
          / gasComponents[nComponent].nHeight);

        boLinkFound = FALSE;
        nLinks = 0;
        while ((nLinks < gasComponents[nComponent].nLinksIn)
          && (!boLinkFound))
        {
          if (gasComponents[nComponent].asLinksIn[nLinks].nLinkIn == nLinkIn)
          {
            boLinkFound = TRUE;
          }
          else
          {
            nLinks++;
          }
        }
        if (boLinkFound)
        {
          gnComponentDrag =
            gasComponents[nComponent].asLinksIn[nLinks].nComponentFrom;

          nLinkOut = gasComponents[nComponent].asLinksIn[nLinks].nLinkOut;

          // Remove the link
          RemoveLink (gnComponentDrag, nComponent,
            gasComponents[nComponent].asLinksIn[nLinks].nSearchLinkOut,
            nLinks);

          // Redrag the link
          RedragLinkEndStart (gnComponentDrag, nLinkOut, nXPos, nYPos);
        }
      }
      else
      {
        if ((nXRel > (gasComponents[nComponent].nWidth - LINKSTART_WIDTH))
          && (gasComponents[nComponent].nMaxLinksOut > 0))
        {
          // Link start
          DragLinkEndStart (nComponent, nXPos, nYPos);
        }
        else
        {
          // Component
          DragComponentStart (nComponent);
        }
      }
    }
  }

  // Double click?
  if (nButton & (wimp_DOUBLE_SELECT | wimp_DOUBLE_ADJUST))
  {
    // Check if we are inside a component
    boFound = FALSE;
    nComponent = 0;
    while ((nComponent < gnComponents) && !boFound)
    {
      nXRel = nXPos - gasComponents[nComponent].nXPos;
      nYRel = nYPos - gasComponents[nComponent].nYPos;
      if ((nXRel > 0) && (nXRel < gasComponents[nComponent].nWidth)
        && (nYRel > 0) && (nYRel < gasComponents[nComponent].nHeight))
      {
        // Bingo! We've hit a component!
        boFound = TRUE;
      }
      else
      {
        nComponent++;
      }
    }
    if (boFound)
    {
      // Open the configuration window for the component (if there is one)
      ConfigComponent (nComponent);
    }
  }
}

//////////////////////////////////////////////////////////////////
// Start dragging an end link
void DragLinkEndStart (int nComponent, int nXPos, int nYPos)
{
  bool                        boMore;
  wimp_draw                   sWimpDraw;
  wimp_drag                   sWimpDrag;
  wimp_window_state           sState;
  int                         nVisMinX;
  int                         nVisMaxY;
  int                         nScrollX;
  int                         nScrollY;
  wimp_auto_scroll_info       sScrollInfo;

  sState.w = gwhMain;
  xwimp_get_window_state (& sState);
  nVisMinX = sState.visible.x0;
  nVisMaxY = sState.visible.y1;
  nScrollX = sState.xscroll;
  nScrollY = sState.yscroll;

  sScrollInfo.w = gwhMain;
  sScrollInfo.pause_zone_sizes.x0 = 80;
  sScrollInfo.pause_zone_sizes.y0 = 80;
  sScrollInfo.pause_zone_sizes.x1 = 80;
  sScrollInfo.pause_zone_sizes.y1 = 80;
  sScrollInfo.pause_duration = 0;
  sScrollInfo.state_change = NULL;
  sScrollInfo.handle = NULL;
  xwimp_auto_scroll ((wimp_AUTO_SCROLL_ENABLE_HORIZONTAL
    | wimp_AUTO_SCROLL_ENABLE_VERTICAL), & sScrollInfo, NULL);

  geSaveType = SAVETYPE_LINKEND;

  // Link start
  gnLinkOut = (int)(((nYPos - gasComponents[nComponent].nYPos)
    * gasComponents[nComponent].nMaxLinksOut)
    / gasComponents[nComponent].nHeight);
  gnDragLinkXStart = gasComponents[nComponent].nXPos
    + gasComponents[nComponent].nWidth;
  gnDragLinkYStart = gasComponents[nComponent].nYPos
    + (((float)(gnLinkOut) + 0.5f)
    * (float)gasComponents[nComponent].nHeight
    / (float)(gasComponents[nComponent].nMaxLinksOut));
  gnDragLinkXEnd = nXPos;
  gnDragLinkYEnd = nYPos;

  sWimpDrag.w = gwhMain;
  sWimpDrag.type = wimp_DRAG_USER_POINT;
  sWimpDrag.initial.x0 = nVisMinX - nScrollX + gnDragLinkXStart;
  sWimpDrag.initial.x1 = nVisMinX - nScrollX + gnDragLinkXEnd;
  sWimpDrag.initial.y0 = nVisMaxY - nScrollY + gnDragLinkYEnd;
  sWimpDrag.initial.y1 = nVisMaxY - nScrollY + gnDragLinkYStart;
  sWimpDrag.bbox = sState.visible;
  sWimpDrag.draw = NULL;
  sWimpDrag.undraw = NULL;
  sWimpDrag.redraw = NULL;

  err (xwimp_drag_box (& sWimpDrag));

  // Update the background
  sWimpDraw.w = gwhMain;
  sWimpDraw.box.x0 = nScrollX;
  sWimpDraw.box.y0 = sState.visible.y0 - nVisMaxY + nScrollY;
  sWimpDraw.box.x1 = sState.visible.x1 - nVisMinX + nScrollX;
  sWimpDraw.box.y1 = nScrollY;

  err (xwimp_update_window (& sWimpDraw, & boMore));

  while (boMore)
  {
    // Draw the link
    xcolourtrans_set_gcol (DRAG_ARROW_COLOUR, 0, os_ACTION_EXCLUSIVE_DISJOIN,
      NULL, NULL);
    PlotArrow (nVisMinX - nScrollX + gnDragLinkXStart,
      nVisMaxY - nScrollY + gnDragLinkYStart,
      nVisMinX - nScrollX + gnDragLinkXEnd,
      nVisMaxY - nScrollY + gnDragLinkYEnd);

    err (xwimp_get_rectangle (& sWimpDraw, & boMore));
  }
}

//////////////////////////////////////////////////////////////////
// Start dragging an end link that already exists
void RedragLinkEndStart (int nComponent, int nLinkOut, int nXPos, int nYPos)
{
  bool                        boMore;
  wimp_draw                   sWimpDraw;
  wimp_drag                   sWimpDrag;
  wimp_window_state           sState;
  int                         nVisMinX;
  int                         nVisMaxY;
  int                         nScrollX;
  int                         nScrollY;
  wimp_auto_scroll_info       sScrollInfo;

  sState.w = gwhMain;
  xwimp_get_window_state (& sState);
  nVisMinX = sState.visible.x0;
  nVisMaxY = sState.visible.y1;
  nScrollX = sState.xscroll;
  nScrollY = sState.yscroll;

//  // Update the background
//  sWimpDraw.w = gwhMain;
//  sWimpDraw.box.x0 = nScrollX;
//  sWimpDraw.box.y0 = sState.visible.y0 - nVisMaxY + nScrollY;
//  sWimpDraw.box.x1 = sState.visible.x1 - nVisMinX + nScrollX;
//  sWimpDraw.box.y1 = nScrollY;
//
//  err (xwimp_update_window (& sWimpDraw, & boMore));
//
//  while (boMore)
//  {
//    // Redraw the background
//    xwimp_set_colour (wimp_COLOUR_WHITE);
//    xos_plot (os_PLOT_POINT | os_MOVE_TO,
//      sWimpDraw.box.x1 - 1, sWimpDraw.box.y1);
//    xos_plot (os_PLOT_RECTANGLE | os_PLOT_TO,
//      sWimpDraw.box.x0, sWimpDraw.box.y0);
//    RedrawMain (& sWimpDraw);
//
//    err (xwimp_get_rectangle (& sWimpDraw, & boMore));
//  }

  sScrollInfo.w = gwhMain;
  sScrollInfo.pause_zone_sizes.x0 = 80;
  sScrollInfo.pause_zone_sizes.y0 = 80;
  sScrollInfo.pause_zone_sizes.x1 = 80;
  sScrollInfo.pause_zone_sizes.y1 = 80;
  sScrollInfo.pause_duration = 0;
  sScrollInfo.state_change = NULL;
  sScrollInfo.handle = NULL;
  xwimp_auto_scroll ((wimp_AUTO_SCROLL_ENABLE_HORIZONTAL
    | wimp_AUTO_SCROLL_ENABLE_VERTICAL), & sScrollInfo, NULL);

  geSaveType = SAVETYPE_LINKEND;

  // Link start
  gnLinkOut = nLinkOut;
  gnDragLinkXStart = gasComponents[nComponent].nXPos
    + gasComponents[nComponent].nWidth;
  gnDragLinkYStart = gasComponents[nComponent].nYPos
    + (((float)(gnLinkOut) + 0.5f)
    * (float)gasComponents[nComponent].nHeight
    / (float)(gasComponents[nComponent].nMaxLinksOut));
  gnDragLinkXEnd = nXPos;
  gnDragLinkYEnd = nYPos;

  sWimpDrag.w = gwhMain;
  sWimpDrag.type = wimp_DRAG_USER_POINT;
  sWimpDrag.initial.x0 = nVisMinX - nScrollX + gnDragLinkXStart;
  sWimpDrag.initial.x1 = nVisMinX - nScrollX + gnDragLinkXEnd;
  sWimpDrag.initial.y0 = nVisMaxY - nScrollY + gnDragLinkYEnd;
  sWimpDrag.initial.y1 = nVisMaxY - nScrollY + gnDragLinkYStart;
  sWimpDrag.bbox = sState.visible;
  sWimpDrag.draw = NULL;
  sWimpDrag.undraw = NULL;
  sWimpDrag.redraw = NULL;

  err (xwimp_drag_box (& sWimpDrag));

  // Update the background
  sWimpDraw.w = gwhMain;
  sWimpDraw.box.x0 = nScrollX;
  sWimpDraw.box.y0 = sState.visible.y0 - nVisMaxY + nScrollY;
  sWimpDraw.box.x1 = sState.visible.x1 - nVisMinX + nScrollX;
  sWimpDraw.box.y1 = nScrollY;

  err (xwimp_update_window (& sWimpDraw, & boMore));

  while (boMore)
  {
    // Redraw the background
    xwimp_set_colour (wimp_COLOUR_WHITE);
    xos_plot (os_PLOT_POINT | os_MOVE_TO,
      sWimpDraw.box.x1 - 1, sWimpDraw.box.y1);
    xos_plot (os_PLOT_RECTANGLE | os_PLOT_TO,
      sWimpDraw.box.x0, sWimpDraw.box.y0);
    RedrawMain (& sWimpDraw);
//    // Draw the link
//    xcolourtrans_set_gcol (DRAG_ARROW_COLOUR, 0, os_ACTION_EXCLUSIVE_DISJOIN,
//      NULL, NULL);
//    PlotArrow (nVisMinX - nScrollX + gnDragLinkXStart,
//      nVisMaxY - nScrollY + gnDragLinkYStart,
//      nVisMinX - nScrollX + gnDragLinkXEnd,
//      nVisMaxY - nScrollY + gnDragLinkYEnd);

    err (xwimp_get_rectangle (& sWimpDraw, & boMore));
  }
}

//////////////////////////////////////////////////////////////////
// End dragging an end link
void DragLinkEndEnd (void)
{
  bool                        boMore;
  wimp_draw                   sWimpDraw;
  wimp_window_state           sState;
  int                         nVisMinX;
  int                         nVisMaxY;
  int                         nScrollX;
  int                         nScrollY;
  wimp_auto_scroll_info       sScrollInfo;
  wimp_pointer                sPointer;

  int                         nXPos;
  int                         nYPos;
  int                         nComponent;
  int                         nXRel;
  int                         nYRel;
  bool                        boFound;
  int                         nLinkIn;

  sState.w = gwhMain;
  xwimp_get_window_state (& sState);
  nVisMinX = sState.visible.x0;
  nVisMaxY = sState.visible.y1;
  nScrollX = sState.xscroll;
  nScrollY = sState.yscroll;

  xwimp_get_pointer_info (& sPointer);

  nXPos = (sPointer.pos.x - nVisMinX + nScrollX);
  nYPos = (sPointer.pos.y - nVisMaxY + nScrollY);

  sScrollInfo.w = gwhMain;
  sScrollInfo.pause_zone_sizes.x0 = 80;
  sScrollInfo.pause_zone_sizes.y0 = 80;
  sScrollInfo.pause_zone_sizes.x1 = 80;
  sScrollInfo.pause_zone_sizes.y1 = 80;
  sScrollInfo.pause_duration = 0;
  sScrollInfo.state_change = NULL;
  sScrollInfo.handle = NULL;
  xwimp_auto_scroll (0, & sScrollInfo, NULL);

  geSaveType = SAVETYPE_INVALID;

  // Find out if we need to create a link
  // Check if we are inside a component
  boFound = FALSE;
  nComponent = 0;
  while ((nComponent < gnComponents) && !boFound)
  {
    nXRel = nXPos - gasComponents[nComponent].nXPos;
    nYRel = nYPos - gasComponents[nComponent].nYPos;
    if ((nXRel > 0) && (nXRel < gasComponents[nComponent].nWidth)
      && (nYRel > 0) && (nYRel < gasComponents[nComponent].nHeight))
    {
      // Bingo! We've hit a component!
      boFound = TRUE;
    }
    else
    {
      nComponent++;
    }
  }
  if (boFound)
  {
    gnComponentLinkTo = nComponent;
    boFound = FALSE;

    // Is this an end connector?
    if ((nXRel < LINKEND_WIDTH)
      && (gasComponents[nComponent].nMaxLinksIn > 0))
    {
      // Link start
      nXPos = gasComponents[nComponent].nXPos;
      nLinkIn = (int)((nYRel * gasComponents[nComponent].nMaxLinksIn)
        / gasComponents[nComponent].nHeight);
      nYPos = gasComponents[nComponent].nYPos
        + (((float)(nLinkIn) + 0.5f)
        * (float)gasComponents[nComponent].nHeight
        / (float)(gasComponents[nComponent].nMaxLinksIn));

      CreateFixedLink (gnComponentDrag, gnLinkOut,
        gnComponentLinkTo, nLinkIn);

      boFound = TRUE;
    }
  }

  // Update the background
  sWimpDraw.w = gwhMain;
  sWimpDraw.box.x0 = nScrollX;
  sWimpDraw.box.y0 = sState.visible.y0 - nVisMaxY + nScrollY;
  sWimpDraw.box.x1 = sState.visible.x1 - nVisMinX + nScrollX;
  sWimpDraw.box.y1 = nScrollY;

  err (xwimp_update_window (& sWimpDraw, & boMore));

  while (boMore)
  {
    // Draw the link
    xcolourtrans_set_gcol (DRAG_ARROW_COLOUR, 0, os_ACTION_EXCLUSIVE_DISJOIN,
      NULL, NULL);
    PlotArrow (nVisMinX - nScrollX + gnDragLinkXStart,
      nVisMaxY - nScrollY + gnDragLinkYStart,
      nVisMinX - nScrollX + gnDragLinkXEnd,
      nVisMaxY - nScrollY + gnDragLinkYEnd);

    if (boFound)
    {
      xcolourtrans_set_gcol (LINK_COLOUR, 0, os_ACTION_OVERWRITE,
        NULL, NULL);
      PlotArrow (nVisMinX - nScrollX + gnDragLinkXStart,
        nVisMaxY - nScrollY + gnDragLinkYStart,
        nVisMinX - nScrollX + gnDragLinkXEnd,
        nVisMaxY - nScrollY + gnDragLinkYEnd);
    }

    err (xwimp_get_rectangle (& sWimpDraw, & boMore));
  }
}

//////////////////////////////////////////////////////////////////
// Draw routine for dragged links
void DragLinkUpdate (void)
{
  bool                        boMore;
  wimp_draw                   sWimpDraw;
  wimp_pointer                sPointer;
  wimp_window_state           sState;
  int                         nVisMinX;
  int                         nVisMaxY;
  int                         nScrollX;
  int                         nScrollY;
  int                         nXPos;
  int                         nYPos;
  int                         nComponent;
  int                         nXRel;
  int                         nYRel;
  bool                        boFound;
  int                         nLinkIn;

  sState.w = gwhMain;
  xwimp_get_window_state (& sState);
  nVisMinX = sState.visible.x0;
  nVisMaxY = sState.visible.y1;
  nScrollX = sState.xscroll;
  nScrollY = sState.yscroll;

  xwimp_get_pointer_info (& sPointer);

  nXPos = (sPointer.pos.x - nVisMinX + nScrollX);
  nYPos = (sPointer.pos.y - nVisMaxY + nScrollY);

  // Is the link close enough to click in place?
  // Check if we are inside a component
  boFound = FALSE;
  nComponent = 0;
  while ((nComponent < gnComponents) && !boFound)
  {
    nXRel = nXPos - gasComponents[nComponent].nXPos;
    nYRel = nYPos - gasComponents[nComponent].nYPos;
    if ((nXRel > 0) && (nXRel < gasComponents[nComponent].nWidth)
      && (nYRel > 0) && (nYRel < gasComponents[nComponent].nHeight))
    {
      // Bingo! We've hit a component!
      boFound = TRUE;
    }
    else
    {
      nComponent++;
    }
  }
  if (boFound)
  {
    gnComponentLinkTo = nComponent;

    // Is this an end connector?
    if ((nXRel < LINKEND_WIDTH)
      && (gasComponents[nComponent].nMaxLinksIn > 0))
    {
      // Link start
      nXPos = gasComponents[nComponent].nXPos;
      nLinkIn = (int)((nYRel * gasComponents[nComponent].nMaxLinksIn)
        / gasComponents[nComponent].nHeight);
      nYPos = gasComponents[nComponent].nYPos
        + (((float)(nLinkIn) + 0.5f)
        * (float)gasComponents[nComponent].nHeight
        / (float)(gasComponents[nComponent].nMaxLinksIn));
    }
  }

  // Find out if we need to redraw
  if ((nXPos != gnDragLinkXEnd) || (nYPos != gnDragLinkYEnd))
  {
    sWimpDraw.w = gwhMain;
    sWimpDraw.box.x0 = nScrollX;
    sWimpDraw.box.y0 = sState.visible.y0 - nVisMaxY + nScrollY;
    sWimpDraw.box.x1 = sState.visible.x1 - nVisMinX + nScrollX;
    sWimpDraw.box.y1 = nScrollY;

    err (xwimp_update_window (& sWimpDraw, & boMore));

    while (boMore)
    {
      // Undraw the previous link
      xcolourtrans_set_gcol (DRAG_ARROW_COLOUR, 0,
        os_ACTION_EXCLUSIVE_DISJOIN,
        NULL, NULL);
      PlotArrow (nVisMinX - nScrollX + gnDragLinkXStart,
        nVisMaxY - nScrollY + gnDragLinkYStart,
        nVisMinX - nScrollX + gnDragLinkXEnd,
        nVisMaxY - nScrollY + gnDragLinkYEnd);

      // Redraw the new link
      PlotArrow (nVisMinX - nScrollX + gnDragLinkXStart,
        nVisMaxY - nScrollY + gnDragLinkYStart,
        nVisMinX - nScrollX + nXPos,
        nVisMaxY - nScrollY + nYPos);

      err (xwimp_get_rectangle (& sWimpDraw, & boMore));
    }
    gnDragLinkXEnd = nXPos;
    gnDragLinkYEnd = nYPos;
  }
}

//////////////////////////////////////////////////////////////////
// Plot an arrow
void PlotArrow (int nXStart, int nYStart, int nXEnd, int nYEnd)
{
  draw_line_style             sLine;
  int                         anPath[14];
  os_trfm                     sMatrix;
  float                       fDistance;

  fDistance = sqrt ((double)((((nXStart - nXEnd) * (nXStart - nXEnd))
    + ((nYStart - nYEnd) * (nYStart - nYEnd))))) / (float)BEZIER_SCALE;

  if (fDistance > 1.0f)
  {
    fDistance = 1.0f;
  }

  if (fDistance < 0.1f)
  {
    fDistance = 0.1f;
  }

  anPath[0] = (int)draw_MOVE_TO;
  anPath[1] = nXStart * 256;
  anPath[2] = nYStart * 256;
  anPath[3] = (int)draw_BEZIER_TO;
  anPath[4] = (nXStart + (int)(BEZIER_START_X * fDistance)) * 256;
  anPath[5] = (nYStart + (int)(BEZIER_START_Y * fDistance))* 256;
  anPath[6] = (nXEnd + (int)(BEZIER_END_X * fDistance)
    + LINKEND_XCLICK) * 256;
  anPath[7] = (nYEnd + (int)(BEZIER_END_Y * fDistance)) * 256;
  anPath[8] = (nXEnd + LINKEND_XCLICK) * 256;
  anPath[9] = nYEnd * 256;
  anPath[10] = (int)draw_CLOSE_GAP;
  anPath[11] = 0;
  anPath[12] = (int)draw_END_PATH;
  anPath[13] = 0;

  sMatrix.entries[0][0] = 1 << 16;
  sMatrix.entries[0][1] = 0;
  sMatrix.entries[1][0] = 0;
  sMatrix.entries[1][1] = 1 << 16;
  sMatrix.entries[2][0] = 0;
  sMatrix.entries[2][1] = 0;

  sLine.join_style = draw_JOIN_ROUND;
  sLine.end_cap_style = draw_CAP_TRIANGULAR;
  sLine.start_cap_style = draw_CAP_BUTT;
  sLine.mitre_limit = 0;
  sLine.start_cap_width = LINK_ARROWWIDTH * 256;
  sLine.start_cap_length = LINK_ARROWLENGTH * 256;
  sLine.end_cap_width = LINK_ARROWWIDTH * 256;
  sLine.end_cap_length = LINK_ARROWLENGTH * 256;

  draw_stroke ((draw_path *)(anPath),
    (1 << 31),
    & sMatrix, 0, LINK_LINEWIDTH * 256,
    & sLine, NULL);
}

//////////////////////////////////////////////////////////////////
// Plot an outward stub arrow
void PlotStubOut (int nXStart, int nYStart, int nXEnd, int nYEnd)
{
  draw_line_style             sLine;
  int                         anPath[10];
  os_trfm                     sMatrix;
  float                       fDistance;

  fDistance = sqrt ((double)((((nXStart - nXEnd) * (nXStart - nXEnd))
    + ((nYStart - nYEnd) * (nYStart - nYEnd))))) / (float)BEZIER_SCALE;

  if (fDistance > 1.0f)
  {
    fDistance = 1.0f;
  }

  if (fDistance < 0.1f)
  {
    fDistance = 0.1f;
  }

  anPath[0] = (int)draw_MOVE_TO;
  anPath[1] = nXStart * 256;
  anPath[2] = nYStart * 256;
  anPath[3] = (int)draw_LINE_TO;
  anPath[4] = (nXEnd + LINK_STUBOUTXSUB) * 256;
  anPath[5] = nYEnd * 256;
  anPath[6] = (int)draw_CLOSE_GAP;
  anPath[7] = 0;
  anPath[8] = (int)draw_END_PATH;
  anPath[9] = 0;

  sMatrix.entries[0][0] = 1 << 16;
  sMatrix.entries[0][1] = 0;
  sMatrix.entries[1][0] = 0;
  sMatrix.entries[1][1] = 1 << 16;
  sMatrix.entries[2][0] = 0;
  sMatrix.entries[2][1] = 0;

  sLine.join_style = draw_JOIN_ROUND;
  sLine.end_cap_style = draw_CAP_TRIANGULAR;
  sLine.start_cap_style = draw_CAP_BUTT;
  sLine.mitre_limit = 0;
  sLine.start_cap_width = LINK_STUBARROWWIDTH * 256;
  sLine.start_cap_length = LINK_STUBARROWLENGTH * 256;
  sLine.end_cap_width = LINK_STUBARROWWIDTH * 256;
  sLine.end_cap_length = LINK_STUBARROWLENGTH * 256;

  draw_stroke ((draw_path *)(anPath),
    (1 << 31),
    & sMatrix, 0, LINK_STUBLINEWIDTH * 256,
    & sLine, NULL);
}

//////////////////////////////////////////////////////////////////
// Plot an inward stub arrow
void PlotStubIn (int nXStart, int nYStart, int nXEnd, int nYEnd)
{
  draw_line_style             sLine;
  int                         anPath[10];
  os_trfm                     sMatrix;
  float                       fDistance;

  fDistance = sqrt ((double)((((nXStart - nXEnd) * (nXStart - nXEnd))
    + ((nYStart - nYEnd) * (nYStart - nYEnd))))) / (float)BEZIER_SCALE;

  if (fDistance > 1.0f)
  {
    fDistance = 1.0f;
  }

  if (fDistance < 0.1f)
  {
    fDistance = 0.1f;
  }

  anPath[0] = (int)draw_MOVE_TO;
  anPath[1] = nXStart * 256;
  anPath[2] = nYStart * 256;
  anPath[3] = (int)draw_LINE_TO;
  anPath[4] = (nXEnd + LINK_STUBINXSUB) * 256;
  anPath[5] = nYEnd * 256;
  anPath[6] = (int)draw_CLOSE_GAP;
  anPath[7] = 0;
  anPath[8] = (int)draw_END_PATH;
  anPath[9] = 0;

  sMatrix.entries[0][0] = 1 << 16;
  sMatrix.entries[0][1] = 0;
  sMatrix.entries[1][0] = 0;
  sMatrix.entries[1][1] = 1 << 16;
  sMatrix.entries[2][0] = 0;
  sMatrix.entries[2][1] = 0;

  sLine.join_style = draw_JOIN_ROUND;
  sLine.end_cap_style = draw_CAP_TRIANGULAR;
  sLine.start_cap_style = draw_CAP_BUTT;
  sLine.mitre_limit = 0;
  sLine.start_cap_width = LINK_STUBARROWWIDTH * 256;
  sLine.start_cap_length = LINK_STUBARROWLENGTH * 256;
  sLine.end_cap_width = LINK_STUBARROWWIDTH * 256;
  sLine.end_cap_length = LINK_STUBARROWLENGTH * 256;

  draw_stroke ((draw_path *)(anPath),
    (1 << 31),
    & sMatrix, 0, LINK_STUBLINEWIDTH * 256,
    & sLine, NULL);
}

//////////////////////////////////////////////////////////////////
// Start dragging a component
void DragComponentStart (int nComponent)
{
  bool                        boMore;
  wimp_draw                   sWimpDraw;
  wimp_auto_scroll_info       sScrollInfo;
  os_box                      sDragBox;
  wimp_window_state           sState;
  int                         nVisMinX;
  int                         nVisMaxY;
  int                         nScrollX;
  int                         nScrollY;

  sState.w = gwhMain;
  xwimp_get_window_state (& sState);
  nVisMinX = sState.visible.x0;
  nVisMaxY = sState.visible.y1;
  nScrollX = sState.xscroll;
  nScrollY = sState.yscroll;

  sScrollInfo.w = gwhMain;
  sScrollInfo.pause_zone_sizes.x0 = 80;
  sScrollInfo.pause_zone_sizes.y0 = 80;
  sScrollInfo.pause_zone_sizes.x1 = 80;
  sScrollInfo.pause_zone_sizes.y1 = 80;
  sScrollInfo.pause_duration = 0;
  sScrollInfo.state_change = NULL;
  sScrollInfo.handle = NULL;
  xwimp_auto_scroll ((wimp_AUTO_SCROLL_ENABLE_HORIZONTAL
    | wimp_AUTO_SCROLL_ENABLE_VERTICAL), & sScrollInfo, NULL);

  // Drag the component
  sDragBox.x0 = nVisMinX - nScrollX + gasComponents[nComponent].nXPos;
  sDragBox.y0 = nVisMaxY - nScrollY + gasComponents[nComponent].nYPos;
  sDragBox.x1 = nVisMinX - nScrollX + gasComponents[nComponent].nXPos
    + gasComponents[nComponent].nWidth;
  sDragBox.y1 = nVisMaxY - nScrollY + gasComponents[nComponent].nYPos
    + gasComponents[nComponent].nHeight;
  xdragasprite_start ((dragasprite_HPOS_CENTRE
    | dragasprite_VPOS_CENTRE
    | dragasprite_BOUND_TO_WINDOW
    | dragasprite_BOUND_POINTER
//    | dragasprite_DROP_SHADOW
    | dragasprite_NO_DITHER),
    gasComponents[nComponent].pcSprites,
    "compose",
    & sDragBox, NULL);
  geSaveType = SAVETYPE_COMPONENT;

  // Redraw without the component
  gasComponents[nComponent].boHidden = TRUE;
  sWimpDraw.w = gwhMain;
  sWimpDraw.box.x0 = gasComponents[nComponent].nXPos;
  sWimpDraw.box.y0 = gasComponents[nComponent].nYPos;
  sWimpDraw.box.x1 = gasComponents[nComponent].nXPos
    + gasComponents[nComponent].nWidth;
  sWimpDraw.box.y1 = gasComponents[nComponent].nYPos
    + gasComponents[nComponent].nHeight;

  err (xwimp_update_window (& sWimpDraw, & boMore));

  while (boMore)
  {
    // Redraw the main window
    xwimp_set_colour (wimp_COLOUR_WHITE);
    xos_plot (os_PLOT_POINT | os_MOVE_TO,
      sWimpDraw.box.x1 - 1, sWimpDraw.box.y1);
    xos_plot (os_PLOT_RECTANGLE | os_PLOT_TO,
      sWimpDraw.box.x0, sWimpDraw.box.y0);
    RedrawMain (& sWimpDraw);
    err (xwimp_get_rectangle (& sWimpDraw, & boMore));
  }
}

//////////////////////////////////////////////////////////////////
// Move a component to a new position
void MoveComponent (int nComponent, int nXPos, int nYPos)
{
  bool                        boMore;
  wimp_draw                   sWimpDraw;
  int                         nXPrev;
  int                         nYPrev;
  wimp_window_state           sState;
  int                         nVisMinX;
  int                         nVisMaxY;
  int                         nScrollX;
  int                         nScrollY;

  nXPrev = gasComponents[nComponent].nXPos;
  nYPrev = gasComponents[nComponent].nYPos;

  sState.w = gwhMain;
  xwimp_get_window_state (& sState);
  nVisMinX = sState.visible.x0;
  nVisMaxY = sState.visible.y1;
  nScrollX = sState.xscroll;
  nScrollY = sState.yscroll;

  gasComponents[nComponent].nXPos = nXPos - nVisMinX + nScrollX;
  gasComponents[nComponent].nYPos = nYPos - nVisMaxY + nScrollY;

  sWimpDraw.w = gwhMain;
//  sWimpDraw.box.x0 = gasComponents[nComponent].nXPos;
//  sWimpDraw.box.y0 = gasComponents[nComponent].nYPos;
//  sWimpDraw.box.x1 = gasComponents[nComponent].nXPos
//    + gasComponents[nComponent].nWidth;
//  sWimpDraw.box.y1 = gasComponents[nComponent].nYPos
//    + gasComponents[nComponent].nHeight;

  sWimpDraw.box.x0 = nScrollX;
  sWimpDraw.box.y0 = sState.visible.y0 - nVisMaxY + nScrollY;
  sWimpDraw.box.x1 = sState.visible.x1 - nVisMinX + nScrollX;
  sWimpDraw.box.y1 = nScrollY;

  err (xwimp_update_window (& sWimpDraw, & boMore));

  while (boMore)
  {
    // Redraw the main window
    xwimp_set_colour (wimp_COLOUR_WHITE);
    xos_plot (os_PLOT_POINT | os_MOVE_TO,
      sWimpDraw.box.x1 - 1, sWimpDraw.box.y1);
    xos_plot (os_PLOT_RECTANGLE | os_PLOT_TO,
      sWimpDraw.box.x0, sWimpDraw.box.y0);
    RedrawMain (& sWimpDraw);
    err (xwimp_get_rectangle (& sWimpDraw, & boMore));
  }

//  sWimpDraw.box.x0 = nXPrev;
//  sWimpDraw.box.y0 = nYPrev;
//  sWimpDraw.box.x1 = nXPrev + gasComponents[nComponent].nWidth;
//  sWimpDraw.box.y1 = nYPrev + gasComponents[nComponent].nHeight;
//
//  err (xwimp_update_window (& sWimpDraw, & boMore));
//
//  while (boMore)
//  {
//    // Redraw the main window
//    xwimp_set_colour (wimp_COLOUR_WHITE);
//    xos_plot (os_PLOT_POINT | os_MOVE_TO,
//      sWimpDraw.box.x1 - 1, sWimpDraw.box.y1);
//    xos_plot (os_PLOT_RECTANGLE | os_PLOT_TO,
//      sWimpDraw.box.x0, sWimpDraw.box.y0);
//    RedrawMain (& sWimpDraw);
//    err (xwimp_get_rectangle (& sWimpDraw, & boMore));
//  }
}

//////////////////////////////////////////////////////////////////
// Poll 0: Null Poll
void NullPoll (wimp_block *pcBlock)
{
  pcBlock = pcBlock;

  if (geSaveType == SAVETYPE_LINKEND)
  {
    DragLinkUpdate ();
  }

  ShowInfo ();
}

//////////////////////////////////////////////////////////////////
// Create a permanent link between the two components
void CreateFixedLink (int nComponentFrom, int nLinkOut, int nComponentTo, int nLinkIn)
{
  int                         nLinksOut;
  int                         nLinksIn;

  nLinksOut = gasComponents[nComponentFrom].nLinksOut;
  nLinksIn = gasComponents[nComponentTo].nLinksIn;
  if ((nLinksOut < LINK_MAX) && (nLinksIn < LINK_MAX))
  {
    ResetLinkOut (& gasComponents[nComponentFrom].asLinksOut[nLinksOut]);
    gasComponents[nComponentFrom].asLinksOut[nLinksOut].nComponentTo
      = nComponentTo;
    gasComponents[nComponentFrom].asLinksOut[nLinksOut].nLinkOut = nLinkOut;
    gasComponents[nComponentFrom].asLinksOut[nLinksOut].nLinkIn = nLinkIn;
    gasComponents[nComponentFrom].nLinksOut++;

    gasComponents[nComponentFrom].asLinksOut[nLinksOut].nSearchLinkIn =
      nLinksIn;

    ResetLinkIn (& gasComponents[nComponentTo].asLinksIn[nLinksIn]);
    gasComponents[nComponentTo].asLinksIn[nLinksIn].nComponentFrom
      = nComponentFrom;
    gasComponents[nComponentTo].asLinksIn[nLinksIn].nLinkOut = nLinkOut;
    gasComponents[nComponentTo].asLinksIn[nLinksIn].nLinkIn = nLinkIn;
    gasComponents[nComponentTo].asLinksIn[nLinksIn].nSearchLinkOut =
      nLinksOut;
    gasComponents[nComponentTo].nLinksIn++;
  }
  else
  {
    ShowWarningTag ("Er4");
  }
}

//////////////////////////////////////////////////////////////////
// Attempt to load a component
bool LoadComponent (char * szFileName, int nXPos, int nYPos)
{
  bool                        boLoadSuccess = TRUE;
  int                         nObjType;
  int                         nFileType;
  int                         nSize;
  os_error                    *psError;
  char                        szFile[256];

  wimp_draw                   sWimpDraw;
  wimp_window_state           sState;
  int                         nVisMinX;
  int                         nVisMaxY;
  int                         nScrollX;
  int                         nScrollY;
  bool                        boMore;

  MemFile                     *psFile;
  int                         nSection;
  char                        szValue[256];
  char                        *pcInfo;
  int                         nInfoPos;
  int                         nInfoLen;
  int                         nLink;
  char                        szTag[64];

  sState.w = gwhMain;
  xwimp_get_window_state (& sState);
  nVisMinX = sState.visible.x0;
  nVisMaxY = sState.visible.y1;
  nScrollX = sState.xscroll;
  nScrollY = sState.yscroll;

  if (gnComponents < COMPONENT_MAX)
  {
    // Is it a directory?
    psError =  xosfile_read_stamped_no_path (szFileName, & nObjType, NULL,
      NULL, & nSize, NULL, & nFileType);

    if (psError)
    {
      err (psError);
      boLoadSuccess = FALSE;
    }

    if ((boLoadSuccess) && (nObjType == fileswitch_IS_DIR))
    {
      ReleaseComponent (& gasComponents[gnComponents]);

      // Load the sprites
      strcpy (szFile, szFileName);
      strcat (szFile, ".Compose");
      gasComponents[gnComponents].pcSprites = LoadSprites (szFile);
      if (gasComponents[gnComponents].pcSprites)
      {
        GenerateTransTable (& gasComponents[gnComponents].psTransTable,
          gasComponents[gnComponents].pcSprites);
        strcpy (szFile, "compose");
        psError = xosspriteop_read_sprite_info (osspriteop_USER_AREA,
          gasComponents[gnComponents].pcSprites, (osspriteop_id)szFile,
          & gasComponents[gnComponents].nWidth,
          & gasComponents[gnComponents].nHeight,
          NULL, NULL);
        if (psError)
        {
          err (psError);
          boLoadSuccess = FALSE;
        }
      }
      else
      {
        boLoadSuccess = FALSE;
      }
      if (boLoadSuccess)
      {
        gasComponents[gnComponents].nXPos = nXPos - nVisMinX + nScrollX
          - gasComponents[gnComponents].nWidth;
        gasComponents[gnComponents].nYPos = nYPos - nVisMaxY + nScrollY
          - gasComponents[gnComponents].nHeight;

        gasComponents[gnComponents].nWidth *= 2;
        gasComponents[gnComponents].nHeight *= 2;

        gasComponents[gnComponents].nMaxLinksOut = 1;
        gasComponents[gnComponents].nMaxLinksIn = 1;
        strcpy (szFile, szFileName);
        strcat (szFile, ".Details");
        psFile = memopen (szFile, "r");
        if (psFile)
        {
          nSection = FindSectionMem (psFile, "Component");
          FindValueMem (psFile, nSection, "LinksOut", szValue,
            sizeof (szValue));
          sscanf (szValue, "%d", & gasComponents[gnComponents].nMaxLinksOut);
          FindValueMem (psFile, nSection, "LinksIn", szValue,
            sizeof (szValue));
          sscanf (szValue, "%d", & gasComponents[gnComponents].nMaxLinksIn);
          FindValueMem (psFile, nSection, "Dir", szValue,
            sizeof (szValue));
          strncpy (gasComponents[gnComponents].szDir, szValue,
            COMPONENT_DIR_MAX);

          pcInfo = malloc (INFO_MEM_MAX);
          nInfoPos = 0;

          nSection = FindSectionMem (psFile, "Info");
          FindValueMem (psFile, nSection, "Component", szValue,
            sizeof (szValue));
          nInfoLen = strlen (szValue);
          if (nInfoLen > 0)
          {
            memcpy (pcInfo, szValue, nInfoLen + 1);
            nInfoPos += (nInfoLen + 1);
          }

          nSection = FindSectionMem (psFile, "Links out");
          for (nLink = 0; nLink < gasComponents[gnComponents].nMaxLinksOut;
            nLink++)
          {
            sprintf (szTag, "%d", nLink);
            FindValueMem (psFile, nSection, szTag, szValue,
              sizeof (szValue));
            nInfoLen = strlen (szValue);
            if (nInfoLen > 0)
            {
              memcpy (pcInfo + nInfoPos, szValue, nInfoLen + 1);
              gasComponents[gnComponents].anInfoOffset[nLink] = nInfoPos;
              nInfoPos += (nInfoLen + 1);
            }
            else
            {
              gasComponents[gnComponents].anInfoOffset[nLink] = 0;
            }
          }

          nSection = FindSectionMem (psFile, "Links in");
          for (nLink = 0; nLink < gasComponents[gnComponents].nMaxLinksIn;
            nLink++)
          {
            sprintf (szTag, "%d", nLink);
            FindValueMem (psFile, nSection, szTag, szValue,
              sizeof (szValue));
            nInfoLen = strlen (szValue);
            if (nInfoLen > 0)
            {
              memcpy (pcInfo + nInfoPos, szValue, nInfoLen + 1);
              gasComponents[gnComponents].anInfoOffset[nLink +
                gasComponents[gnComponents].nMaxLinksOut] = nInfoPos;
              nInfoPos += (nInfoLen + 1);
            }
            else
            {
              gasComponents[gnComponents].anInfoOffset[nLink +
                gasComponents[gnComponents].nMaxLinksOut] = 0;
            }
          }

          if (nInfoPos > 0)
          {
            gasComponents[gnComponents].pcInfo = malloc (nInfoPos);
            memcpy (gasComponents[gnComponents].pcInfo, pcInfo, nInfoPos);
          }
          else
          {
            gasComponents[gnComponents].pcInfo = NULL;
          }
          free (pcInfo);
          memclose (psFile);
        }
        else
        {
          boLoadSuccess = FALSE;
        }
      }
    }
  }
  else
  {
    ShowWarningTag ("Er5");
    boLoadSuccess = FALSE;
  }

  if (boLoadSuccess)
  {
    gnComponents++;
    sWimpDraw.w = gwhMain;
    sWimpDraw.box.x0 = gasComponents[gnComponents - 1].nXPos;
    sWimpDraw.box.y0 = gasComponents[gnComponents - 1].nYPos;
    sWimpDraw.box.x1 = gasComponents[gnComponents - 1].nXPos
      + gasComponents[gnComponents - 1].nWidth;
    sWimpDraw.box.y1 = gasComponents[gnComponents - 1].nYPos
      + gasComponents[gnComponents - 1].nHeight;

    err (xwimp_update_window (& sWimpDraw, & boMore));

    while (boMore)
    {
      // Redraw the main window
//      xwimp_set_colour (wimp_COLOUR_WHITE);
//      xos_plot (os_PLOT_POINT | os_MOVE_TO,
//        sWimpDraw.box.x1 - 1, sWimpDraw.box.y1);
//      xos_plot (os_PLOT_RECTANGLE | os_PLOT_TO,
//        sWimpDraw.box.x0, sWimpDraw.box.y0);
      RedrawMain (& sWimpDraw);
      err (xwimp_get_rectangle (& sWimpDraw, & boMore));
    }
  }

  SetStatus (STATUS_GUESS);

  return boLoadSuccess;
}

//////////////////////////////////////////////////////////////////
// Receive message LINK_CONTROL
void ReceiveLinkControl (wimp_block *pcBlock)
{
  int                         nAction;

  nAction = ((int *)(pcBlock->message.data.reserved))[0];

  switch (nAction)
  {
    case 0: // Init
      gnLinkHandle = ((int *)(pcBlock->message.data.reserved))[0];
      ghLinkTask = pcBlock->message.sender;
      ActOnInit ();
      break;
    case 1: // Config
      ActOnConfig ();
      break;
  }

  REPORTVAR ("RECV- LinkControl handle = %d", gnLinkHandle);
  REPORTVAR ("      LinkControl action = %d", nAction);
}

//////////////////////////////////////////////////////////////////
// Receive message and forward it along the correct link
void ReceiveLinkForward (wimp_block *pcBlock)
{
  int                         nLinkHandle;
  int                         nLink;
  int                         nComponentTo;
  int                         nLinkIn;
  int                         nLinkSearch;
  wimp_t                      hSendTo;

  // Pass the message on to the correct application

  nLinkHandle = ((int *)(pcBlock->message.data.reserved))[0];
  if (nLinkHandle < gnComponents)
  {
    nLink = ((int *)(pcBlock->message.data.reserved))[1];

    // Find the correct outward links
    for (nLinkSearch = 0; nLinkSearch < gasComponents[nLinkHandle].nLinksOut;
      nLinkSearch++)
    {
      if (gasComponents[nLinkHandle].asLinksOut[nLinkSearch].nLinkOut
        == nLink)
      {
        nComponentTo = gasComponents[nLinkHandle].asLinksOut[nLinkSearch].
          nComponentTo;
        nLinkIn = gasComponents[nLinkHandle].asLinksOut[nLinkSearch].nLinkIn;
        hSendTo = gasComponents[nComponentTo].hTask;

        // Redirect the message
        ((int *)(pcBlock->message.data.reserved))[0] = nComponentTo;
        ((int *)(pcBlock->message.data.reserved))[1] = nLinkIn;

        xwimp_send_message (wimp_USER_MESSAGE, & pcBlock->message, hSendTo);

        REPORTVAR ("FRWD- Link handle = %d", nComponentTo);
        REPORTVAR ("      Link link = %d", nLinkIn);
      }
    }
  }
}

////////////////////////////////////////////////////////////////////
//// Receive message and forward it along the correct link
//void ReceiveLinkForwardRamFetch (wimp_block *pcBlock)
//{
//  int                         nLinkHandle;
//  int                         nLink;
//  int                         nComponentFrom;
//  int                         nLinkOut;
//  int                         nLinkSearch;
//  wimp_t                      hSendTo;
//
//  // Pass the message on to the correct application
//  // This message will be going along the link in the *wrong direction*
//
//  nLinkHandle = ((int *)(pcBlock->message.data.reserved))[0];
//  if (nLinkHandle < gnComponents)
//  {
//    nLink = ((int *)(pcBlock->message.data.reserved))[1];
//
//    // Find the correct *inward* links
//    for (nLinkSearch = 0; nLinkSearch < gasComponents[nLinkHandle].nLinksIn;
//      nLinkSearch++)
//    {
//      if (gasComponents[nLinkHandle].asLinksIn[nLinkSearch].nLinkIn
//        == nLink)
//      {
//        nComponentFrom = gasComponents[nLinkHandle].asLinksIn[nLinkSearch].
//          nComponentFrom;
//        nLinkOut
//          = gasComponents[nLinkHandle].asLinksIn[nLinkSearch].nLinkOut;
//        hSendTo = gasComponents[nComponentFrom].hTask;
//
//        // Redirect the message
//        ((int *)(pcBlock->message.data.reserved))[0] = nComponentFrom;
//        ((int *)(pcBlock->message.data.reserved))[1] = nLinkOut;
//
//        // We need to add in the sender's task handle
//        ((int *)(pcBlock->message.data.reserved))[4] =
//          (int)pcBlock->message.sender;
//
//        xwimp_send_message (wimp_USER_MESSAGE, & pcBlock->message, hSendTo);
//
//        REPORTVAR ("FRWD: LinkRamFetch handle = %d", nComponentFrom);
//        REPORTVAR ("      LinkRamFetch link = %d", nLinkOut);
//      }
//    }
//  }
//}

//////////////////////////////////////////////////////////////////
// Receive message LINK_DATASAVE
void ReceiveLinkDataSave (wimp_block *pcBlock)
{
  int                         nLinkHandle;
  int                         nLink;
  int                         nSize;
  int                         nSuccess;
  int                         nLinkSearch;
  wimp_message                sMessage;
  int                         nTransBuffer;


  nTransBuffer = GetNextTransBuffer ();
  nLinkHandle = ((int *)(pcBlock->message.data.reserved))[0];
  if ((nLinkHandle < gnComponents) && (nTransBuffer != -1))
  {
    nLink = ((int *)(pcBlock->message.data.reserved))[1];
    nSize = ((int *)(pcBlock->message.data.reserved))[2];

    REPORTVAR ("RECV- LinkDataSave handle = %d", nLinkHandle);
    REPORTVAR ("      LinkDataSave link = %d", nLink);
    REPORTVAR ("      LinkDataSave size = %d", nSize);

    // Find the correct outward links
    for (nLinkSearch = 0; nLinkSearch < gasComponents[nLinkHandle].nLinksOut;
      nLinkSearch++)
    {
      if (gasComponents[nLinkHandle].asLinksOut[nLinkSearch].nLinkOut
        == nLink)
      {
        // Set up a buffer for the data and suck it all in
        // However, we can't use this buffer, since it might move
        // so we use the (potentially) smaller fixed buffer
        gasComponents[nLinkHandle].asLinksOut[nLinkSearch].nDataSize = 0;
        nSuccess = flex_alloc ((flex_ptr)
          (& gasComponents[nLinkHandle].asLinksOut[nLinkSearch].pcData),
          nSize);
        if (nSuccess == 1)
        {
          gasComponents[nLinkHandle].asLinksOut[nLinkSearch].nTransBuffer =
            nTransBuffer;
        }
        else
        {
          ShowWarningTag ("Er6");
        }
      }
    }

    // Reply with a RamFetch message
    sMessage = pcBlock->message;

    ((int *)(sMessage.data.reserved))[2] =
      (int)gapcTransBuffer[nTransBuffer];
    ((int *)(sMessage.data.reserved))[3] = LINK_BUFFER_SIZE;

    gasComponents[nLinkHandle].asLinksOut[nLinkSearch].nTransRef =
      pcBlock->message.your_ref;

    sMessage.size = 36;
    sMessage.your_ref = pcBlock->message.my_ref;
    sMessage.action = message_LINK_RAMFETCH;

    xwimp_send_message (wimp_USER_MESSAGE, & sMessage,
      pcBlock->message.sender);

    REPORTVAR ("SEND- LinkRamFetch handle = %d", nLinkHandle);
    REPORTVAR ("      LinkRamFetch link = %d", nLink);
    REPORTVAR ("      LinkRamFetch size = %d", LINK_BUFFER_SIZE);

    gaboTransBufferUsed[nTransBuffer] = TRUE;
  }
}

//////////////////////////////////////////////////////////////////
// Receive message LINK_RAMFETCH
void ReceiveLinkRamFetch (wimp_block *pcBlock)
{
  int                         nLinkHandle;
  int                         nLink;
  int                         nSize;
  char                        *pcBuffer;
  int                         nSendLen;
  LinkIn                      *psLinkIn;
  LinkOut                     *psLinkOut;
  wimp_message                sMessage;
  int                         nYourRef;
  int                         nLinkSearch;
  int                         nSearchLink;

  nLinkHandle = ((int *)(pcBlock->message.data.reserved))[0];
  if (nLinkHandle < gnComponents)
  {
    // We need to find the correct link based in the my_ref
    nYourRef = pcBlock->message.your_ref;

    // Take a reference so that we can respond
    nSearchLink = -1;
    for (nLinkSearch = 0; nLinkSearch < gasComponents[nLinkHandle].nLinksIn;
      nLinkSearch++)
    {
      if (gasComponents[nLinkHandle].asLinksIn[nLinkSearch].nTransRef
        == nYourRef)
      {
        REPORT ("Found inward link to get");
        gasComponents[nLinkHandle].asLinksIn[nLinkSearch].nTransRef =
          sMessage.my_ref;
        nSearchLink = nLinkSearch;
      }
    }

    if (nSearchLink != -1)
    {
      nLink = ((int *)(pcBlock->message.data.reserved))[1];
      pcBuffer = (char *)(((int *)(pcBlock->message.data.reserved))[2]);
      nSize = ((int *)(pcBlock->message.data.reserved))[3];

      REPORTVAR ("RECV- LinkRamFetch handle = %d", nLinkHandle);
      REPORTVAR ("      LinkRamFetch link = %d", nLink);
      REPORTVAR ("      LinkRamFetch size = %d", nSize);

      psLinkIn = & gasComponents[nLinkHandle].asLinksIn[nSearchLink];
      psLinkOut = & gasComponents[psLinkIn->nComponentFrom].
        asLinksOut[psLinkIn->nSearchLinkOut];

      // Transfer data
      if ((psLinkOut->nDataSize - psLinkIn->nDataSent) < nSize)
      {
        nSendLen = (psLinkOut->nDataSize - psLinkIn->nDataSent);
      }
      else
      {
        nSendLen = nSize;
      }

      if (nSendLen > 0)
      {
        xwimp_transfer_block (gnTaskHandle, psLinkOut->pcData +
          psLinkIn->nDataSent, pcBlock->message.sender,
          pcBuffer, nSendLen);
      }

      // Reply with a RamTransmit message
      ((int *)(pcBlock->message.data.reserved))[2] = (int)pcBuffer;
      ((int *)(pcBlock->message.data.reserved))[3] = nSendLen;

      pcBlock->message.size = 36;
      pcBlock->message.your_ref = pcBlock->message.my_ref;
      pcBlock->message.action = message_LINK_RAMTRANSMIT;

      xwimp_send_message (wimp_USER_MESSAGE, & pcBlock->message,
        pcBlock->message.sender);

      REPORTVAR ("SEND- LinkRamTransmit handle = %d", nLinkHandle);
      REPORTVAR ("      LinkRamTransmit link = %d", nLink);
      REPORTVAR ("      LinkRamTransmit size = %d", nSendLen);

      psLinkIn->nDataSent += nSendLen;

      if (nSendLen < nSize)
      {
        // Everything has been transmitted
        psLinkIn->nTransRef = -1;
        psLinkIn->nDataSent = 0;
        flex_free ((flex_ptr)(& (psLinkOut->pcData)));
        psLinkOut->pcData = NULL;
        psLinkOut->nDataSize = 0;
        psLinkOut->nTransRef = -1;
      }
      else
      {
        psLinkIn->nTransRef = pcBlock->message.my_ref;
      }
    }
  }
}

//////////////////////////////////////////////////////////////////
// Receive message LINK_RAMTRANSMIT
void ReceiveLinkRamTransmit (wimp_block *pcBlock)
{
  int                         nLinkHandle;
  int                         nLink;
  int                         nSize;
  char                        *pcBuffer;
  int                         nLinkSearch;

  nLinkHandle = ((int *)(pcBlock->message.data.reserved))[0];
  if (nLinkHandle < gnComponents)
  {
    nLink = ((int *)(pcBlock->message.data.reserved))[1];
    nSize = ((int *)(pcBlock->message.data.reserved))[3];
    pcBuffer = (char *)(((int *)(pcBlock->message.data.reserved))[2]);

    REPORTVAR ("RECV- LinkRamTransmit handle = %d", nLinkHandle);
    REPORTVAR ("      LinkRamTransmit link = %d", nLink);
    REPORTVAR ("      LinkRamTransmit size = %d", nSize);

    // Find the correct outward links
    for (nLinkSearch = 0; nLinkSearch < gasComponents[nLinkHandle].nLinksOut;
      nLinkSearch++)
    {
      if (gasComponents[nLinkHandle].asLinksOut[nLinkSearch].nLinkOut
        == nLink)
      {
        if (nSize > 0)
        {
          // Copy the data over
          memcpy (gasComponents[nLinkHandle].asLinksOut[nLinkSearch].pcData
            + gasComponents[nLinkHandle].asLinksOut[nLinkSearch].nDataSize,
            pcBuffer, nSize);
          gasComponents[nLinkHandle].asLinksOut[nLinkSearch].nDataSize +=
            nSize;
        }
      }
    }

    if (nSize == LINK_BUFFER_SIZE)
    {
      // The buffer was full, so we should ask for some more

      // Reply with a RamFetch message
      ((int *)(pcBlock->message.data.reserved))[3] = LINK_BUFFER_SIZE;

      pcBlock->message.size = 40;
      pcBlock->message.your_ref = pcBlock->message.my_ref;
      pcBlock->message.action = message_LINK_RAMFETCH;

      xwimp_send_message (wimp_USER_MESSAGE, & pcBlock->message,
        pcBlock->message.sender);
      gasComponents[nLinkHandle].asLinksOut[nLinkSearch].nTransRef =
        pcBlock->message.my_ref;

      REPORTVAR ("SEND- LinkRamFetch handle = %d", nLinkHandle);
      REPORTVAR ("      LinkRamFetch link = %d", nLink);
      REPORTVAR ("      LinkRamFetch size = %d", LINK_BUFFER_SIZE);
    }

    if (nSize < LINK_BUFFER_SIZE)
    {
      for (nLinkSearch = 0; nLinkSearch <
        gasComponents[nLinkHandle].nLinksOut; nLinkSearch++)
      {
        if (gasComponents[nLinkHandle].asLinksOut[nLinkSearch].nLinkOut
          == nLink)
        {
          gasComponents[nLinkHandle].asLinksOut[nLinkSearch].nTransRef = -1;
          gaboTransBufferUsed[gasComponents[nLinkHandle].
            asLinksOut[nLinkSearch].nTransBuffer] = FALSE;
          gasComponents[nLinkHandle].asLinksOut[nLinkSearch].nTransBuffer
            = -1;
          LinkRamTransmitComplete (nLinkHandle, nLinkSearch);
        }
      }
    }
  }
}

//////////////////////////////////////////////////////////////////
// We've received all of the data, we need to send it on
void LinkRamTransmitComplete (int nComponent, int nSearchLink)
{
  wimp_message                sMessage;
  int                         nComponentTo;
  int                         nLinkIn;
  int                         nLinkOut;
  wimp_t                      hSendTo;
  int                         nSize;
  int                         nLinkSearch;

  nComponentTo = gasComponents[nComponent].asLinksOut[nSearchLink].
    nComponentTo;
  nLinkIn = gasComponents[nComponent].asLinksOut[nSearchLink].nLinkIn;
  nLinkOut = gasComponents[nComponent].asLinksOut[nSearchLink].nLinkOut;
  hSendTo = gasComponents[nComponentTo].hTask;
  nSize = gasComponents[nComponent].asLinksOut[nSearchLink].nDataSize;

  ((int *)(sMessage.data.reserved))[0] = nComponentTo;
  ((int *)(sMessage.data.reserved))[1] = nLinkIn;
  ((int *)(sMessage.data.reserved))[2] = nSize;

  sMessage.size = 32;
  sMessage.your_ref = 0;
  sMessage.action = message_LINK_DATASAVE;

  xwimp_send_message (wimp_USER_MESSAGE, & sMessage, hSendTo);

  // Take a reference so that we can respond
  for (nLinkSearch = 0; nLinkSearch < gasComponents[nComponentTo].nLinksIn;
    nLinkSearch++)
  {
    if ((gasComponents[nComponentTo].asLinksIn[nLinkSearch].nComponentFrom
      == nComponent)
      && (gasComponents[nComponentTo].asLinksIn[nLinkSearch].nLinkOut
      == nLinkOut)
      && (gasComponents[nComponentTo].asLinksIn[nLinkSearch].nLinkIn
      == nLinkIn))
    {
      REPORT ("Found inward link to set");
      gasComponents[nComponentTo].asLinksIn[nLinkSearch].nTransRef =
        sMessage.my_ref;
    }
  }

  REPORTVAR ("SEND- LinkDataSave handle = %d", nComponentTo);
  REPORTVAR ("      LinkDataSave link = %d", nLinkIn);
  REPORTVAR ("      LinkDataSave size = %d", nSize);
}

//////////////////////////////////////////////////////////////////
// Send message LINK_OPEN
void LinkOpen (int nLink)
{
  wimp_message                sMessage;

  if (ghLinkTask != NULL)
  {
    ((int *)(sMessage.data.reserved))[0] = gnLinkHandle;
    ((int *)(sMessage.data.reserved))[1] = nLink;

    sMessage.size = 28;
    sMessage.your_ref = 0;
    sMessage.action = message_LINK_OPEN;

    xwimp_send_message (wimp_USER_MESSAGE, & sMessage, ghLinkTask);

    REPORTVAR ("SEND- LinkOpen handle = %d", gnLinkHandle);
    REPORTVAR ("      LinkOpen link = %d", nLink);
  }
}

//////////////////////////////////////////////////////////////////
// Send message LINK_SEND
void LinkSend (int nLink, char * pcData, int nSize)
{
  wimp_message                sMessage;

  if ((ghLinkTask != NULL) && (nSize <= LINK_SEND_MAX))
  {
    ((int *)(sMessage.data.reserved))[0] = gnLinkHandle;
    ((int *)(sMessage.data.reserved))[1] = nLink;
    ((int *)(sMessage.data.reserved))[2] = nSize;
    memcpy (sMessage.data.reserved + 12, pcData, nSize);

    sMessage.size = WORDALIGN ((32 + nSize));
    sMessage.your_ref = 0;
    sMessage.action = message_LINK_SEND;

    xwimp_send_message (wimp_USER_MESSAGE, & sMessage, ghLinkTask);

    REPORTVAR ("SEND- LinkSend handle = %d", gnLinkHandle);
    REPORTVAR ("      LinkSend link = %d", nLink);
    REPORTVAR ("      LinkSend size = %d", nSize);
  }
}

//////////////////////////////////////////////////////////////////
// Send message LINK_CLOSE
void LinkClose (int nLink)
{
  wimp_message                sMessage;

  if (ghLinkTask != NULL)
  {
    ((int *)(sMessage.data.reserved))[0] = gnLinkHandle;
    ((int *)(sMessage.data.reserved))[1] = nLink;

    sMessage.size = 28;
    sMessage.your_ref = 0;
    sMessage.action = message_LINK_CLOSE;

    xwimp_send_message (wimp_USER_MESSAGE, & sMessage, ghLinkTask);

    REPORTVAR ("SEND- LinkClose handle = %d", gnLinkHandle);
    REPORTVAR ("      LinkClose link = %d", nLink);
  }
}

//////////////////////////////////////////////////////////////////
// Act on initialisation
void ActOnInit (void)
{
}

//////////////////////////////////////////////////////////////////
// Act on config request
void ActOnConfig (void)
{
}

//////////////////////////////////////////////////////////////////
// Act on the opening of a link
void ActOnLinkOpen (int nLink)
{
  nLink = nLink;
}

//////////////////////////////////////////////////////////////////
// Act on the closing of a link
void ActOnLinkClose (int nLink)
{
  nLink = nLink;
}

//////////////////////////////////////////////////////////////////
// Act on the receiving of data on a link
void ActOnLinkSend (int nLink, char * pcData, int nSize)
{
  nLink = nLink;
  pcData = pcData;
  nSize = nSize;
}

//////////////////////////////////////////////////////////////////
// Load all of the components
void LoadAllComponents (void)
{
  char                        szVar[255];
  int                         nUsed;

  if (gnLoadTaskCount < 0)
  {
    gnLoadTaskCount = 0;
    while ((gnLoadTaskCount < gnComponents)
      && (gasComponents[gnLoadTaskCount].hTask != NULL))
    {
      gnLoadTaskCount++;
    }

    if (gnLoadTaskCount < gnComponents)
    {
      xos_read_var_val (gasComponents[gnLoadTaskCount].szDir, 0, -1, 0,
        os_VARTYPE_STRING, & nUsed, NULL, NULL);

      if (nUsed)
      {
        strcpy (szVar, gasComponents[gnLoadTaskCount].szDir);
        strcat (szVar, ".!RunComp");
        err (xwimp_start_task (szVar, NULL));
      }
    }
  }
}

//////////////////////////////////////////////////////////////////
// Start all of the components processing
void RunAllComponents (void)
{
  int                         nComponent;
  wimp_message                sMessage;

  // Check the components are loaded
  if (ganComponentsLoaded == gnComponents)
  {
    // Send "init" message to all of the components
    for (nComponent = 0 ; nComponent < gnComponents; nComponent++)
    {
      if (gasComponents[nComponent].hTask != NULL)
      {
        ((int *)(sMessage.data.reserved))[0] = nComponent;
        ((int *)(sMessage.data.reserved))[1] = 0; // Init

        sMessage.size = 28;
        sMessage.your_ref = 0;
        sMessage.action = message_LINK_CONTROL;

        xwimp_send_message (wimp_USER_MESSAGE, & sMessage,
          gasComponents[nComponent].hTask);

        REPORTVAR ("SEND: LinkControl handle = %d", nComponent);
        REPORTVAR ("      LinkControl task = %d",
          (int)gasComponents[nComponent].hTask);
        REPORTVAR ("      LinkControl action = %d", 0);
      }
    }
  }
  else
  {
    ShowWarningTag ("Er7");
  }
}

//////////////////////////////////////////////////////////////////
// Quit all of the components
void QuitAllComponents (void)
{
  int                         nComponent;
  wimp_message                sMessage;

  // Send "quit" message to all of the components
  for (nComponent = 0 ; nComponent < gnComponents; nComponent++)
  {
    if (gasComponents[nComponent].hTask != NULL)
    {
      sMessage.size = 20;
      sMessage.your_ref = 0;
      sMessage.action = message_QUIT;

      xwimp_send_message (wimp_USER_MESSAGE, & sMessage,
        gasComponents[nComponent].hTask);
    }
  }
}

//////////////////////////////////////////////////////////////////
// Open the configuration window for a component
void ConfigComponent (int nComponent)
{
  wimp_message                sMessage;

  if (gasComponents[nComponent].hTask != NULL)
  {
    ((int *)(sMessage.data.reserved))[0] = nComponent;
    ((int *)(sMessage.data.reserved))[1] = 1; // Config

    sMessage.size = 28;
    sMessage.your_ref = 0;
    sMessage.action = message_LINK_CONTROL;

    xwimp_send_message (wimp_USER_MESSAGE, & sMessage,
      gasComponents[nComponent].hTask);

    REPORTVAR ("SEND: LinkControl handle = %d", nComponent);
    REPORTVAR ("      LinkControl task = %d",
      (int)gasComponents[nComponent].hTask);
    REPORTVAR ("      LinkControl action = %d", 1);
  }
  else
  {
    ShowWarningTag ("Er8");
  }
}

//////////////////////////////////////////////////////////////////
// Received a Task_Initialise message
void TaskInitialise (wimp_block *pcBlock)
{
  char                        szVar[255];
  int                         nUsed;

  if (gnLoadTaskCount >= 0)
  {
    gasComponents[gnLoadTaskCount].hTask = pcBlock->message.sender;
    REPORTVAR ("Task handle %d", (int)pcBlock->message.sender);

    SendConfigLoad (gnLoadTaskCount);

    gnLoadTaskCount++;
    ganComponentsLoaded++;

    while ((gnLoadTaskCount < gnComponents)
      && (gasComponents[gnLoadTaskCount].hTask != NULL))
    {
      gnLoadTaskCount++;
    }

    if (gnLoadTaskCount < gnComponents)
    {
      xos_read_var_val (gasComponents[gnLoadTaskCount].szDir, 0, -1, 0,
        os_VARTYPE_STRING, & nUsed, NULL, NULL);

      if (nUsed)
      {
        strcpy (szVar, gasComponents[gnLoadTaskCount].szDir);
        strcat (szVar, ".!RunComp");
        err (xwimp_start_task (szVar, NULL));
      }
    }
    else
    {
      // All of them have now been loaded
      gnLoadTaskCount = -1;
      SetStatus (STATUS_GUESS);
    }
  }
}

//////////////////////////////////////////////////////////////////
// Received a Task_CloseDown message
void TaskCloseDown (wimp_block *pcBlock)
{
  int                         nCount;

  for (nCount = 0; nCount < gnComponents; nCount++)
  {
    if (gasComponents[nCount].hTask == pcBlock->message.sender)
    {
      gasComponents[nCount].hTask = NULL;
      ganComponentsLoaded--;
    }
  }

  SetStatus (STATUS_GUESS);
}

//////////////////////////////////////////////////////////////////
// Return the next available trans buffer, or -1 if there are none left
int GetNextTransBuffer (void)
{
  int                         nCount;
  int                         nFound;

  nFound = -1;
  for (nCount = 0; (nCount < TRANS_MAX) && (nFound == -1); nCount++)
  {
    if (!gaboTransBufferUsed[nCount])
    {
      nFound = nCount;
    }
  }

  return nCount;
}

//////////////////////////////////////////////////////////////////
// Sets the status display (red, amber, green)
void SetStatus (STATUS eStatus)
{
  if (eStatus != STATUS_GUESS)
  {
    geStatus = eStatus;
  }
  else
  {
    if (gnComponents == 0)
    {
      geStatus = STATUS_NONE;
    }
    else
    {
      if ((ganComponentsLoaded > 0) && (ganComponentsLoaded < gnComponents))
      {
        geStatus = STATUS_SOME;
      }
      if (ganComponentsLoaded == 0)
      {
        geStatus = STATUS_NONE;
      }
      if (ganComponentsLoaded == gnComponents)
      {
        geStatus = STATUS_READY;
      }
    }
  }

  switch (geStatus)
  {
    case STATUS_NONE:
      SetIconText ("red", gwhMaHe, 0);
      SetIconText ("off", gwhMaHe, 1);
      SetIconText ("off", gwhMaHe, 2);
      break;
    case STATUS_SOME:
      SetIconText ("off", gwhMaHe, 0);
      SetIconText ("amber", gwhMaHe, 1);
      SetIconText ("off", gwhMaHe, 2);
      break;
    case STATUS_READY:
      SetIconText ("off", gwhMaHe, 0);
      SetIconText ("off", gwhMaHe, 1);
      SetIconText ("green", gwhMaHe, 2);
      break;
    default:
      SetIconText ("off", gwhMaHe, 0);
      SetIconText ("off", gwhMaHe, 1);
      SetIconText ("off", gwhMaHe, 2);
      break;
  }
}

//////////////////////////////////////////////////////////////////
// Remove a particular link
void RemoveLink (int nComponentFrom, int nComponentTo, int nLinkOut, int nLinkIn)
{
  int                         nLink;

  if (gasComponents[nComponentFrom].nLinksOut > 0)
  {
    // Remove the LinkOut link
    gasComponents[nComponentFrom].nLinksOut--;
    for (nLink = nLinkOut; nLink < gasComponents[nComponentFrom].nLinksOut;
      nLink++)
    {
      // Clear the details of the current link
      ReleaseLinkOut (& gasComponents[nComponentFrom].asLinksOut[nLink]);
      // Shift the link down
      MemMoveLinkOut (& gasComponents[nComponentFrom].asLinksOut[nLink],
        & gasComponents[nComponentFrom].asLinksOut[nLink + 1]);
      // Renumber the index
      gasComponents[
      gasComponents[nComponentFrom].asLinksOut[nLink].nComponentTo
      ].asLinksIn[
      gasComponents[nComponentFrom].asLinksOut[nLink].nSearchLinkIn
      ].nSearchLinkOut--;
    }
    ReleaseLinkOut (& gasComponents[nComponentFrom].asLinksOut[nLink]);
  }

  if (gasComponents[nComponentTo].nLinksIn > 0)
  {
    // Clear the details of the current link
    gasComponents[nComponentTo].nLinksIn--;
    for (nLink = nLinkIn; nLink < gasComponents[nComponentTo].nLinksIn;
      nLink++)
    {
      // Clear the details of the current link
      ReleaseLinkIn (& gasComponents[nComponentTo].asLinksIn[nLink]);
      // Shift the link down
      MemMoveLinkIn (& gasComponents[nComponentTo].asLinksIn[nLink],
        & gasComponents[nComponentTo].asLinksIn[nLink + 1]);
      // Renumber the index
      gasComponents[
      gasComponents[nComponentTo].asLinksIn[nLink].nComponentFrom
      ].asLinksOut[
      gasComponents[nComponentTo].asLinksIn[nLink].nSearchLinkOut
      ].nSearchLinkIn--;
    }
    ReleaseLinkIn (& gasComponents[nComponentTo].asLinksIn[nLink]);
  }
}

//////////////////////////////////////////////////////////////////
// Remove a particular component
void RemoveComponent (int nComponent)
{
  int                         nLink;
  int                         nComponentLoop;
  bool                        boMore;
  wimp_draw                   sWimpDraw;
  wimp_window_state           sState;
  int                         nVisMinX;
  int                         nVisMaxY;
  int                         nScrollX;
  int                         nScrollY;
  wimp_message                sMessage;

  // Quit the component if it's running
  if (gasComponents[nComponent].hTask != NULL)
  {
    sMessage.size = 20;
    sMessage.your_ref = 0;
    sMessage.action = message_QUIT;

    xwimp_send_message (wimp_USER_MESSAGE, & sMessage,
      gasComponents[nComponent].hTask);

    // All of the information about the component is about to be removed
    // so we'll just have to assume it quits properly
    gasComponents[nComponent].hTask = NULL;
    ganComponentsLoaded--;
  }

  // First remove all of the links (starting from the end)
  for (nLink = gasComponents[nComponent].nLinksOut - 1; nLink >= 0; nLink--)
  {
    RemoveLink (nComponent,
      gasComponents[nComponent].asLinksOut[nLink].nComponentTo,
      nLink,
      gasComponents[nComponent].asLinksOut[nLink].nSearchLinkIn);
  }

  for (nLink = gasComponents[nComponent].nLinksIn - 1; nLink >= 0; nLink--)
  {
    RemoveLink (
      gasComponents[nComponent].asLinksIn[nLink].nComponentFrom,
      nComponent,
      gasComponents[nComponent].asLinksIn[nLink].nSearchLinkOut,
      nLink);
  }

  // Now remove the component
  gnComponents--;
  for (nComponentLoop = nComponent; nComponentLoop < gnComponents;
    nComponentLoop++)
  {
    // Clear the details of the current component
    ReleaseComponent (& gasComponents[nComponentLoop]);
    // Shift the component down
    MemMoveComponent (& gasComponents[nComponentLoop],
      & gasComponents[nComponentLoop + 1]);
    // Renumber all of the indexes for links out
    for (nLink = 0; nLink < gasComponents[nComponentLoop].nLinksOut; nLink++)
    {
      gasComponents[
      gasComponents[nComponentLoop].asLinksOut[nLink].nComponentTo
      ].asLinksIn[
      gasComponents[nComponentLoop].asLinksOut[nLink].nSearchLinkIn
      ].nComponentFrom--;
    }

    // Renumber all of the indexes for links in
    for (nLink = 0; nLink < gasComponents[nComponentLoop].nLinksIn; nLink++)
    {
      gasComponents[
      gasComponents[nComponentLoop].asLinksIn[nLink].nComponentFrom
      ].asLinksOut[
      gasComponents[nComponentLoop].asLinksIn[nLink].nSearchLinkOut
      ].nComponentTo--;
    }
  }
  ReleaseComponent (& gasComponents[nComponentLoop]);

  // Redraw the entire window
  sState.w = gwhMain;
  xwimp_get_window_state (& sState);
  nVisMinX = sState.visible.x0;
  nVisMaxY = sState.visible.y1;
  nScrollX = sState.xscroll;
  nScrollY = sState.yscroll;

  sWimpDraw.w = gwhMain;

  sWimpDraw.box.x0 = nScrollX;
  sWimpDraw.box.y0 = sState.visible.y0 - nVisMaxY + nScrollY;
  sWimpDraw.box.x1 = sState.visible.x1 - nVisMinX + nScrollX;
  sWimpDraw.box.y1 = nScrollY;

  err (xwimp_update_window (& sWimpDraw, & boMore));

  while (boMore)
  {
    // Redraw the main window
    xwimp_set_colour (wimp_COLOUR_WHITE);
    xos_plot (os_PLOT_POINT | os_MOVE_TO,
      sWimpDraw.box.x1 - 1, sWimpDraw.box.y1);
    xos_plot (os_PLOT_RECTANGLE | os_PLOT_TO,
      sWimpDraw.box.x0, sWimpDraw.box.y0);
    RedrawMain (& sWimpDraw);
    err (xwimp_get_rectangle (& sWimpDraw, & boMore));
  }

  SetStatus (STATUS_GUESS);
  gszConfigFile[0] = 0;
}

//////////////////////////////////////////////////////////////////
// Move a link out somewhere else in memory
int MemMoveLinkOut (LinkOut * psLinkOutTo, LinkOut * psLinkOutFrom)
{
  int                         nSuccess = 1;

  *psLinkOutTo = *psLinkOutFrom;
  psLinkOutTo->pcData = NULL;
  if (psLinkOutFrom->pcData)
  {
    nSuccess = flex_reanchor ((flex_ptr)(& psLinkOutTo->pcData),
      (flex_ptr)(& psLinkOutFrom->pcData));
    if (nSuccess == 0)
    {
      ShowWarningTag ("Er9");
    }
  }
  psLinkOutFrom->pcData = NULL;
  ReleaseLinkOut (psLinkOutFrom);

  return nSuccess;
}

//////////////////////////////////////////////////////////////////
// Move a link in somewhere else in memory
int MemMoveLinkIn (LinkIn * psLinkInTo, LinkIn * psLinkInFrom)
{
  *psLinkInTo = *psLinkInFrom;
  ReleaseLinkIn (psLinkInFrom);

  return 1;
}

//////////////////////////////////////////////////////////////////
// Move a component somewhere else in memory
int MemMoveComponent (Component * psComponentTo, Component * psComponentFrom)
{
//  int                         nSuccess;

  *psComponentTo = *psComponentFrom;
//  psComponentTo->pcSprites = NULL;
//  psComponentTo->psTransTable = NULL;
//  nSuccess = flex_reanchor ((flex_ptr)(& psComponentTo->pcSprites),
//    (flex_ptr)(& psComponentFrom->pcSprites));
  psComponentFrom->pcSprites = NULL;
//  nSuccess &= flex_reanchor ((flex_ptr)(& psComponentTo->psTransTable),
//    (flex_ptr)(& psComponentFrom->psTransTable));
//
//  if (nSuccess == 0)
//  {
//    ShowWarningTag ("Er10");
//  }
  psComponentFrom->psTransTable = NULL;
  psComponentFrom->pcInfo = NULL;

  ReleaseComponent (psComponentFrom);

  return 1;
}

//////////////////////////////////////////////////////////////////
// Show info about a component
void ShowInfoComponent (int nComponent)
{
  char                        szInfo[INFOTEXT_LEN];

  if ((gnInfoComponent != nComponent) || (gnInfoLinkIn != INVALID)
    || (gnInfoLinkOut != INVALID))
  {
    if (gasComponents[nComponent].pcInfo != NULL)
    {
      strcpy (szInfo, Tag ("Comp"));
      strcat (szInfo, gasComponents[nComponent].pcInfo);
      SetIconText (szInfo, gwhMaHe, 6);
    }
    else
    {
      SetIconText (Tag ("NoInfoComp"), gwhMaHe, 6);
    }
    gnInfoComponent = nComponent;
    gnInfoLinkIn = INVALID;
    gnInfoLinkOut = INVALID;
  }
}

//////////////////////////////////////////////////////////////////
// Show info about a link start
void ShowInfoLinkStart (int nComponent, int nLink)
{
  char                        szInfo[INFOTEXT_LEN];

  if ((gnInfoComponent != nComponent) || (gnInfoLinkIn != INVALID)
    || (gnInfoLinkOut != nLink))
  {
    if ((gasComponents[nComponent].pcInfo != NULL)
      && (gasComponents[nComponent].anInfoOffset[nLink] > 0))
    {
      strcpy (szInfo, Tag ("Out"));
      strcat (szInfo, (gasComponents[nComponent].pcInfo +
        gasComponents[nComponent].anInfoOffset[nLink]));
      SetIconText (szInfo, gwhMaHe, 6);
    }
    else
    {
      SetIconText (Tag ("NoInfoOut"), gwhMaHe, 6);
    }
    gnInfoComponent = nComponent;
    gnInfoLinkIn = INVALID;
    gnInfoLinkOut = nLink;
  }
}

//////////////////////////////////////////////////////////////////
// Show info about a link end
void ShowInfoLinkEnd (int nComponent, int nLink)
{
  char                        szInfo[INFOTEXT_LEN];

  if ((gnInfoComponent != nComponent) || (gnInfoLinkIn != nLink)
    || (gnInfoLinkOut != INVALID))
  {
    if ((gasComponents[nComponent].pcInfo != NULL)
      && (gasComponents[nComponent].anInfoOffset[gasComponents[nComponent]
        .nMaxLinksOut + nLink]))
    {
      strcpy (szInfo, Tag ("In"));
      strcat (szInfo, (gasComponents[nComponent].pcInfo +
        gasComponents[nComponent].anInfoOffset[gasComponents[nComponent]
        .nMaxLinksOut + nLink]));
      SetIconText (szInfo, gwhMaHe, 6);
    }
    else
    {
      SetIconText (Tag ("NoInfoIn"), gwhMaHe, 6);
    }
    gnInfoComponent = nComponent;
    gnInfoLinkIn = nLink;
    gnInfoLinkOut = INVALID;
  }
}

//////////////////////////////////////////////////////////////////
// Don't show any info
void ShowInfoNone (void)
{
  if ((gnInfoComponent != INVALID) || (gnInfoLinkIn != INVALID)
    || (gnInfoLinkOut != INVALID))
  {
    SetIconText ("", gwhMaHe, 6);
    gnInfoComponent = INVALID;
    gnInfoLinkIn = INVALID;
    gnInfoLinkOut = INVALID;
  }
}

//////////////////////////////////////////////////////////////////
// Show info about components, links etc.. if there is any
void ShowInfo (void)
{
  int                         nComponent;
  int                         nXPos;
  int                         nYPos;
  int                         nXRel;
  int                         nYRel;
  bool                        boFound;
  int                         nLink;
  wimp_window_state           sState;
  wimp_pointer                sPointer;

  // Get mouse position relative to window
  xwimp_get_pointer_info (& sPointer);
  sState.w = gwhMain;
  xwimp_get_window_state (& sState);
  nXPos = sPointer.pos.x - sState.visible.x0 + sState.xscroll;
  nYPos = sPointer.pos.y - sState.visible.y1 + sState.yscroll;

  // Check if we are inside a component
  boFound = FALSE;
  nComponent = 0;
  while ((nComponent < gnComponents) && !boFound)
  {
    nXRel = nXPos - gasComponents[nComponent].nXPos;
    nYRel = nYPos - gasComponents[nComponent].nYPos;
    if ((nXRel > 0) && (nXRel < gasComponents[nComponent].nWidth)
      && (nYRel > 0) && (nYRel < gasComponents[nComponent].nHeight))
    {
      // Bingo! We've hit a component!
      boFound = TRUE;
    }
    else
    {
      nComponent++;
    }
  }
  if (boFound)
  {
    // Are we moving a component, or a link?
    if ((nXRel < LINKEND_WIDTH)
        && (gasComponents[nComponent].nMaxLinksIn > 0))
    {
      // Link end
      nLink = (int)(((nYPos - gasComponents[nComponent].nYPos)
        * gasComponents[nComponent].nMaxLinksIn)
        / gasComponents[nComponent].nHeight);
      ShowInfoLinkEnd (nComponent, nLink);
    }
    else
    {
      if ((nXRel > (gasComponents[nComponent].nWidth - LINKSTART_WIDTH))
        && (gasComponents[nComponent].nMaxLinksOut > 0))
      {
        // Link start
        nLink = (int)(((nYPos - gasComponents[nComponent].nYPos)
          * gasComponents[nComponent].nMaxLinksOut)
          / gasComponents[nComponent].nHeight);
        ShowInfoLinkStart (nComponent, nLink);
      }
      else
      {
        // Component
        ShowInfoComponent (nComponent);
      }
    }
  }
  else
  {
    ShowInfoNone ();
  }
}

//////////////////////////////////////////////////////////////////
// Create a structure file in memory
MemFile * CreateStructureFile (void)
{
  MemFile                     *psStructure;
  char                        szTag[64];
  char                        szValue[256];
  int                         nComponent;
  int                         nLinks;
  int                         nLinkOut;
  int                         nLinkCount;

  psStructure = memcreate (NULL, 0, "w");

  SaveSectionMem (psStructure, "Components");
  sprintf (szValue, "%d", gnComponents);
  SaveDetailMem (psStructure, "Num", szValue);

  nLinks = 0;
  for (nComponent = 0; nComponent < gnComponents; nComponent++)
  {
    nLinks += gasComponents[nComponent].nLinksOut;
    sprintf (szTag, "%dD", nComponent);
    SaveDetailMem (psStructure, szTag, gasComponents[nComponent].szDir);

    sprintf (szTag, "%dP", nComponent);
    sprintf (szValue, "%d,%d", gasComponents[nComponent].nXPos
      + (gasComponents[nComponent].nWidth / 2),
      gasComponents[nComponent].nYPos
      + (gasComponents[nComponent].nHeight / 2));
    SaveDetailMem (psStructure, szTag, szValue);
  }

  SaveSectionMem (psStructure, "Links");
  sprintf (szValue, "%d", nLinks);
  SaveDetailMem (psStructure, "Num", szValue);

  nLinkCount = 0;
  for (nComponent = 0; nComponent < gnComponents; nComponent++)
  {
    for (nLinkOut = 0; nLinkOut < gasComponents[nComponent].nLinksOut;
      nLinkOut++)
    {
      sprintf (szTag, "%d", nLinkCount);
      sprintf (szValue, "%d-%d -> %d-%d", nComponent,
        gasComponents[nComponent].asLinksOut[nLinkOut].nLinkOut,
        gasComponents[nComponent].asLinksOut[nLinkOut].nComponentTo,
        gasComponents[nComponent].asLinksOut[nLinkOut].nLinkIn);
      SaveDetailMem (psStructure, szTag, szValue);
      nLinkCount++;
    }
  }
  gnConfigNum = 0;

  return psStructure;
}

//////////////////////////////////////////////////////////////////
// Load a structure file in memory
void LoadStructureFile (MemFile * psStructureFile)
{
  int                         nComponents;
  int                         nComponent;
  int                         nSection;
  char                        szValue[256];
  char                        szTag[64];
  bool                        boSuccess;
  int                         nXPos;
  int                         nYPos;
  int                         nLinks;
  int                         nLink;
  int                         nComponentStart;
  wimp_window_state           sState;
  int                         nVisMinX;
  int                         nVisMaxY;
  int                         nScrollX;
  int                         nScrollY;
  bool                        boMore;
  wimp_draw                   sWimpDraw;

  int                         nComponentFrom;
  int                         nComponentTo;
  int                         nLinkFrom;
  int                         nLinkTo;

  nComponentStart = gnComponents;

  if (nComponentStart == 0)
  {
    gnLoadPosShift = 0;
  }
  else
  {
    gszConfigFile[0] = 0;
  }

  sState.w = gwhMain;
  xwimp_get_window_state (& sState);
  nVisMinX = sState.visible.x0;
  nVisMaxY = sState.visible.y1;
  nScrollX = sState.xscroll;
  nScrollY = sState.yscroll;

  nSection = FindSectionMem (psStructureFile, "Components");
  FindValueMem (psStructureFile, nSection, "Num", szValue,
    sizeof (szValue));
  sscanf (szValue, "%d", & nComponents);

  boSuccess = TRUE;
  for (nComponent = 0; ((nComponent < nComponents) && (boSuccess));
    nComponent++)
  {
    sprintf (szTag, "%dP", nComponent);
    FindValueMem (psStructureFile, nSection, szTag, szValue,
      sizeof (szValue));
    sscanf (szValue, "%d,%d", & nXPos, & nYPos);
    sprintf (szTag, "%dD", nComponent);
    FindValueMem (psStructureFile, nSection, szTag, szValue,
      sizeof (szValue));
    boSuccess = LoadComponent (szValue, nXPos + nVisMinX - nScrollX
      + gnLoadPosShift, nYPos + nVisMaxY - nScrollY - gnLoadPosShift);
  }

  if (boSuccess)
  {
    nSection = FindSectionMem (psStructureFile, "Links");
    FindValueMem (psStructureFile, nSection, "Num", szValue,
      sizeof (szValue));
    sscanf (szValue, "%d", & nLinks);

    for (nLink = 0; nLink < nLinks; nLink++)
    {
      sprintf (szTag, "%d", nLink);
      FindValueMem (psStructureFile, nSection, szTag, szValue,
        sizeof (szValue));
      sscanf (szValue, "%d-%d -> %d-%d", & nComponentFrom, & nLinkFrom,
        & nComponentTo, & nLinkTo);
      CreateFixedLink (nComponentStart + nComponentFrom, nLinkFrom,
        nComponentStart + nComponentTo, nLinkTo);
    }

    // Update the background
    sWimpDraw.w = gwhMain;
    sWimpDraw.box.x0 = nScrollX;
    sWimpDraw.box.y0 = sState.visible.y0 - nVisMaxY + nScrollY;
    sWimpDraw.box.x1 = sState.visible.x1 - nVisMinX + nScrollX;
    sWimpDraw.box.y1 = nScrollY;

    err (xwimp_update_window (& sWimpDraw, & boMore));

    while (boMore)
    {
      // Redraw the background
      xwimp_set_colour (wimp_COLOUR_WHITE);
      xos_plot (os_PLOT_POINT | os_MOVE_TO,
        sWimpDraw.box.x1 - 1, sWimpDraw.box.y1);
      xos_plot (os_PLOT_RECTANGLE | os_PLOT_TO,
        sWimpDraw.box.x0, sWimpDraw.box.y0);
      RedrawMain (& sWimpDraw);

      err (xwimp_get_rectangle (& sWimpDraw, & boMore));
    }
  }

  if (nComponentStart == 0)
  {
    gnConfigNum = gnComponents;
  }
  else
  {
    gnConfigNum = 0;
  }

  gnLoadPosShift += LOAD_SHIFT_SHIFT;
  if (gnLoadPosShift > LOAD_SHIFT_MAX)
  {
    gnLoadPosShift = 0;
  }
}

//////////////////////////////////////////////////////////////////
// Send a "config load" message to a component task
void SendConfigLoad (int nComponent)
{
  wimp_message                sMessage;

  if (gasComponents[nComponent].hTask != NULL)
  {
    if (gszConfigFile[0] != 0)
    {
      if (nComponent < gnConfigNum)
      {
        ((int *)(sMessage.data.reserved))[0] = nComponent;
        ((int *)(sMessage.data.reserved))[1] = 3; // Load config
        strncpy (sMessage.data.reserved + 8, gszConfigFile, 236 - 8);
        sMessage.data.reserved[236 - 8] = 0;

        sMessage.size = WORDALIGN ((28 +
          strlen (sMessage.data.reserved + 8) + 1));
        sMessage.your_ref = 0;
        sMessage.action = message_LINK_CONTROL;

        xwimp_send_message (wimp_USER_MESSAGE, & sMessage,
          gasComponents[nComponent].hTask);

        REPORTVAR ("SEND: LinkControl handle = %d", nComponent);
        REPORTVAR ("      LinkControl task = %d",
          (int)gasComponents[nComponent].hTask);
        REPORTVAR ("      LinkControl action = %d", 0);
      }
    }
  }
}
